PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.4.0
Defender Security – Malware Scanner, Login Security & Firewall v5.4.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / model / notification / class-firewall-notification.php

class-firewall-notification.php in Defender Security – Malware Scanner, Login Security & Firewall 5.4.0, at src/model/notification/class-firewall-notification.php

318 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles the scheduling and sending of firewall-related notifications.
4 *
5 * @package WP_Defender\Model\Notification
6 */
7
8 namespace WP_Defender\Model\Notification;
9
10 use WP_Defender\Traits\IO;
11 use WP_Defender\Component\Mail;
12 use WP_Defender\Component\Two_Fa;
13 use WP_Defender\Model\Lockout_Log;
14 use WP_Defender\Model\Email_Track;
15 use WP_Defender\Component\Notification;
16 use WP_Defender\Model\Setting\Login_Lockout;
17 use WP_Defender\Controller\Blocklist_Monitor;
18 use WP_Defender\Model\Setting\Notfound_Lockout;
19
20 /**
21 * Handles the scheduling and sending of firewall-related notifications.
22 */
23 class Firewall_Notification extends \WP_Defender\Model\Notification {
24
25 use IO;
26
27 /**
28 * Table name.
29 *
30 * @var string
31 */
32 protected $table = 'wd_malware_firewall_notification';
33
34 /**
35 * Slug identifier for the firewall notification.
36 *
37 * @var string
38 */
39 public const SLUG = 'firewall-notification';
40
41 /**
42 * Constructor method.
43 * Sets default values for the class.
44 */
45 protected function before_load(): void {
46 $default = array(
47 'title' => esc_html__( 'Firewall - Notification', 'defender-security' ),
48 'slug' => self::SLUG,
49 'status' => self::STATUS_DISABLED,
50 'description' => esc_html__(
51 'Get email when a user or IP is locked out for trying to access your login area.',
52 'defender-security'
53 ),
54 // @since 3.0.0 Fix 'Guest'-line.
55 'in_house_recipients' => is_user_logged_in() ? array( $this->get_default_user() ) : array(),
56 'out_house_recipients' => array(),
57 'type' => 'notification',
58 'dry_run' => false,
59 'configs' => array(
60 'login_lockout' => false,
61 'nf_lockout' => false,
62 'ua_lockout' => false,
63 'limit' => false,
64 'threshold' => 3,
65 'cool_off' => 24,
66 ),
67 );
68 $this->import( $default );
69 }
70
71 /**
72 * Checks whether the notification options are enabled for the specified lockout log.
73 *
74 * @param Lockout_Log $model The lockout log model.
75 *
76 * @return bool True if notification options are enabled; otherwise, false.
77 */
78 public function check_options( Lockout_Log $model ): bool {
79 if ( self::STATUS_ACTIVE !== $this->status ) {
80 return false;
81 }
82 // Check 'Login Protection Lockout'.
83 if ( Lockout_Log::AUTH_LOCK === $model->type && true === $this->configs['login_lockout'] ) {
84 return true;
85 }
86 // Check '404 Protection Lockout'.
87 if ( Lockout_Log::LOCKOUT_404 === $model->type && true === $this->configs['nf_lockout'] ) {
88 return true;
89 }
90 // Check 'User Agent Lockout'.
91 if ( Lockout_Log::LOCKOUT_UA === $model->type && true === $this->configs['ua_lockout'] ) {
92 return true;
93 }
94
95 return false;
96 }
97
98 /**
99 * Constructs and sends the firewall notification email based on the lockout log.
100 *
101 * @param Lockout_Log $model The lockout log model.
102 *
103 * @return void
104 */
105 public function send( Lockout_Log $model ): void {
106 if (
107 true === filter_var( $this->configs['login_lockout'], FILTER_VALIDATE_BOOLEAN )
108 && Lockout_Log::AUTH_LOCK === $model->type
109 ) {
110 $template = 'login-lockout';
111 } elseif (
112 true === filter_var( $this->configs['ua_lockout'], FILTER_VALIDATE_BOOLEAN )
113 && Lockout_Log::LOCKOUT_UA === $model->type
114 ) {
115 $template = 'ua-lockout';
116 } else {
117 $template = 'lockout-404';
118 }
119
120 $service = wd_di()->get( Notification::class );
121 foreach ( $this->in_house_recipients as $user ) {
122 if ( self::USER_SUBSCRIBED !== $user['status'] ) {
123 continue;
124 }
125 $this->send_to_user( $user['email'], $user['name'], $model, $template, $service );
126 }
127
128 foreach ( $this->out_house_recipients as $user ) {
129 if ( self::USER_SUBSCRIBED !== $user['status'] ) {
130 continue;
131 }
132 $this->send_to_user( $user['email'], $user['name'], $model, $template, $service );
133 }
134 }
135
136 /**
137 * Constructs and sends the email to the specified recipient.
138 *
139 * @param string $email The recipient's email address.
140 * @param string $name The recipient's name.
141 * @param Lockout_Log $model The lockout log model.
142 * @param string $template The email template to use.
143 * @param object $service The notification service object.
144 *
145 * @return void
146 */
147 private function send_to_user(
148 string $email,
149 string $name,
150 Lockout_Log $model,
151 string $template,
152 object $service
153 ): void {
154 // Check if this meet the threshold.
155 if ( true === $this->configs['limit'] ) {
156 $count = Email_Track::count(
157 $this->slug,
158 $email,
159 strtotime( '-' . $this->configs['cool_off'] . ' hours' ),
160 time()
161 );
162 if ( $count >= $this->configs['threshold'] ) {
163 // No send.
164 return;
165 }
166 }
167 $network_site_url = network_site_url();
168 if ( 'login-lockout' === $template ) {
169 /* translators: %s: Site URL. */
170 $subject = sprintf( esc_html__( 'Login lockout alert for %s', 'defender-security' ), $network_site_url );
171 // If the log is made from the 2FA module, then we get the settings from it, otherwise from Login_Lockout.
172 $settings = wd_di()->get( Login_Lockout::class );
173 if ( false !== strpos( $model->log, '2fa attempts' ) ) {
174 $component = wd_di()->get( Two_Fa::class );
175 $attempt_limit = $component->get_attempt_limit();
176 $time_limit = $component->get_time_limit() . esc_html__( ' seconds', 'defender-security' );
177 $type = '2fa';
178 } else {
179 $attempt_limit = $settings->attempt;
180 $time_limit = $settings->duration . ' ' . $settings->duration_unit;
181 $type = 'login';
182 }
183 // $text & $string will be escaped at src\view\email\login-lockout.php.
184 /* translators: 1: IP address, 2: Site URL, 3: Total attempt from an IP, 4. Lockout type, 5: Translation string. */
185 $text = __(
186 'The host %1$s has been locked out of %2$s due to more than %3$s failed %4$s attempts. %5$s',
187 'defender-security'
188 );
189 if ( 'permanent' === $settings->lockout_type ) {
190 $string = esc_html__( 'Accordingly, the host has been permanently banned.', 'defender-security' );
191 } else {
192 $string = sprintf(
193 /* translators: %s: Duration. */
194 esc_html__( 'They have been locked out for %s.', 'defender-security' ),
195 '<strong>' . esc_html( $time_limit ) . '</strong>'
196 );
197 }
198 $text = sprintf(
199 $text,
200 '<strong>' . $model->ip . '</strong>',
201 '<a href="' . $network_site_url . '">' . $network_site_url . '</a>',
202 '<strong>' . $attempt_limit . '</strong>',
203 $type,
204 $string
205 );
206 } elseif ( 'ua-lockout' === $template ) {
207 $subject = sprintf(
208 /* translators: %s: Site URL. */
209 esc_html__( 'User Agent lockout alert for %s', 'defender-security' ),
210 $network_site_url
211 );
212 $text = sprintf(
213 /* translators: 1: User agent, 2: Site URL */
214 __( 'The %1$s has been locked out of %2$s.', 'defender-security' ),
215 '<strong>' . $model->user_agent . '</strong>',
216 '<a href="' . $network_site_url . '">' . $network_site_url . '</a>',
217 );
218 } else {
219 /* translators: %s: Site URL. */
220 $subject = sprintf( esc_html__( '404 lockout alert for %s', 'defender-security' ), $network_site_url );
221 $settings = wd_di()->get( Notfound_Lockout::class );
222 /* translators: 1: IP address, 2: Site URL, 3: Total attempt from an IP, 4: Tried, 5. Translation string. */
223 $text = __(
224 'The host %1$s has been locked out of %2$s due to more than %3$s 404 requests for the file %4$s. %5$s',
225 'defender-security'
226 );
227 if ( 'permanent' === $settings->lockout_type ) {
228 $string = esc_html__( 'Accordingly, the host has been permanently banned.', 'defender-security' );
229 } else {
230 $string = sprintf(
231 /* translators: %s: Duration. */
232 __( 'They have been locked out for %s.', 'defender-security' ),
233 '<strong>' . esc_html( $settings->duration . ' ' . $settings->duration_unit ) . '</strong>'
234 );
235 }
236 $text = sprintf(
237 $text,
238 '<strong>' . $model->ip . '</strong>',
239 '<a href="' . $network_site_url . '">' . $network_site_url . '</a>',
240 '<strong>' . $settings->attempt . '</strong>',
241 '<strong>' . $model->tried . '</strong>',
242 $string
243 );
244 }
245
246 $logs_url = network_admin_url( 'admin.php?page=wdf-ip-lockout&view=logs' );
247 // Need for activated Mask Login feature.
248 $logs_url = apply_filters( 'report_email_logs_link', $logs_url, $email );
249 // We don't call the Firewall controller to avoid cyclic dependency. It's a workaround with the simplest controller.
250 $controller = wd_di()->get( Blocklist_Monitor::class );
251 $content_body = $controller->render_partial(
252 'email/login-lockout',
253 array(
254 'name' => $name,
255 // It's escaped value.
256 'text' => $text,
257 'logs_url' => $logs_url,
258 ),
259 false
260 );
261 $unsubscribe_link = $service->create_unsubscribe_url( $this->slug, $email );
262 $content = $controller->render_partial(
263 'email/index',
264 array(
265 'title' => esc_html__( 'Firewall', 'defender-security' ),
266 'content_body' => $content_body,
267 'unsubscribe_link' => $unsubscribe_link,
268 ),
269 false
270 );
271
272 $headers = wd_di()->get( Mail::class )->get_headers(
273 defender_noreply_email( 'wd_lockout_noreply_email' ),
274 self::SLUG
275 );
276
277 $ret = wp_mail( $email, $subject, $content, $headers );
278 if ( $ret ) {
279 $this->save_log( $email );
280 }
281 }
282
283 /**
284 * Define labels.
285 *
286 * @return array The array of settings labels.
287 */
288 public function labels(): array {
289 return array(
290 'notification' => esc_html__( 'Firewall - Notification', 'defender-security' ),
291 'login_lockout_notification' => esc_html__( 'Login Protection Lockout', 'defender-security' ),
292 'ip_lockout_notification' => esc_html__( '404 Detection Lockout', 'defender-security' ),
293 'ua_lockout_notification' => esc_html__( 'User Agent Lockout', 'defender-security' ),
294 'notification_subscribers' => esc_html__( 'Recipients', 'defender-security' ),
295 'cooldown_enabled' => esc_html__( 'Limit email notifications for repeat lockouts', 'defender-security' ),
296 'cooldown_number_lockout' => esc_html__( 'Repeat Lockouts Threshold', 'defender-security' ),
297 'cooldown_period' => esc_html__( 'Repeat Lockouts Period', 'defender-security' ),
298 );
299 }
300
301 /**
302 * Additional converting rules.
303 *
304 * @param array $configs The configuration data.
305 *
306 * @return array The type-casted configuration data.
307 * @since 3.1.0
308 */
309 public function type_casting( array $configs ): array {
310 $configs['login_lockout'] = (bool) $configs['login_lockout'];
311 $configs['nf_lockout'] = (bool) $configs['nf_lockout'];
312 $configs['ua_lockout'] = isset( $configs['ua_lockout'] ) ? (bool) $configs['ua_lockout'] : false;
313 $configs['limit'] = (bool) $configs['limit'];
314
315 return $configs;
316 }
317 }
318