Changeset 3422588
- Timestamp:
- 12/18/2025 07:40:50 AM (3 months ago)
- Location:
- cloudsecure-wp-security/trunk
- Files:
-
- 4 edited
-
cloudsecure-wp.php (modified) (1 diff)
-
modules/cloudsecure-wp.php (modified) (3 diffs)
-
modules/two-factor-authentication.php (modified) (5 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
cloudsecure-wp-security/trunk/cloudsecure-wp.php
r3408912 r3422588 14 14 * Plugin URI: https://wpplugin.cloudsecure.ne.jp/cloudsecure_wp_security 15 15 * Description: 管理画面とログインURLをサイバー攻撃から守る、安心の国産・日本語対応プラグインです。かんたんな設定を行うだけで、不正アクセスや不正ログインからあなたのWordPressを保護し、セキュリティが向上します。また、各機能の有効・無効(ON・OFF)や設定などをお好みにカスタマイズし、いつでも保護状態を管理できます。 16 * Version: 1.3.2 116 * Version: 1.3.22 17 17 * Requires PHP: 7.1 18 18 * Author: CloudSecure,Inc. -
cloudsecure-wp-security/trunk/modules/cloudsecure-wp.php
r3377803 r3422588 63 63 $this->captcha = new CloudSecureWP_CAPTCHA( $info, $this->config ); 64 64 $this->login_log = new CloudSecureWP_Login_Log( $info, $this->config, $this->disable_login ); 65 $this->two_factor_authentication = new CloudSecureWP_Two_Factor_Authentication( $info, $this->config, $this->disable_login );65 $this->two_factor_authentication = new CloudSecureWP_Two_Factor_Authentication( $info, $this->config, $this->disable_login, $this->login_log ); 66 66 $this->server_error_notification = new CloudSecureWP_Server_Error_Notification( $info, $this->config ); 67 67 $this->waf = new CloudSecureWP_Waf( $info, $this->config ); … … 145 145 146 146 add_action( 'wp_login', array( $this->login_log, 'wp_login' ), 1, 1 ); 147 add_action( 'wp_login', array( $this->two_factor_authentication, 'cleanup_expired_sessions' ), 2, 0 ); 147 148 add_action( 'xmlrpc_call', array( $this->login_log, 'xmlrpc_call' ), 10 ); 148 149 add_action( 'wp_login_failed', array( $this->login_log, 'wp_login_failed' ), 20, 1 ); … … 258 259 259 260 if ( $this->two_factor_authentication->is_enabled() && 'xmlrpc.php' !== basename( $_SERVER['SCRIPT_NAME'] ) && ! is_admin() ) { 260 add_filter( 'authenticate', array( $this->two_factor_authentication, 'decode_base64_credentials' ), 0, 3 ); 261 add_action( 'wp_login', array( $this->two_factor_authentication, 'wp_login' ), 0, 2 ); 261 add_filter( 'sanitize_user', array( $this->two_factor_authentication, 'restore_login_name' ), 0, 1 ); 262 add_filter( 'authenticate', array( $this->two_factor_authentication, 'restore_login_session' ), 0, 3 ); 263 add_filter( 'authenticate', array( $this->two_factor_authentication, 'authenticate_with_two_factor' ), 100, 3 ); 262 264 add_action( 'wp_login', array( $this->two_factor_authentication, 'redirect_if_not_two_factor_authentication_registered' ), 10, 2 ); 263 265 } -
cloudsecure-wp-security/trunk/modules/two-factor-authentication.php
r3353611 r3422588 6 6 7 7 class CloudSecureWP_Two_Factor_Authentication extends CloudSecureWP_Common { 8 private const KEY_FEATURE = 'two_factor_authentication'; 8 private const KEY_FEATURE = 'two_factor_authentication'; 9 private const OPTION_PREFIX = 'cloudsecurewp_2fa_data_'; 10 private const SESSION_EXPIRY = 300; 11 private const CLEANUP_TIMEOUT = 60; 12 private const CLEANUP_BATCH_SIZE = 1000; 9 13 10 14 private $config; … … 14 18 */ 15 19 private $disable_login; 16 17 /** 18 * 元の認証情報を保存(Base64デコード済み)19 */ 20 private $ original_credentials = array();21 22 function __construct( array $info, CloudSecureWP_Config $config, CloudSecureWP_Disable_Login $disable_login ) {20 21 /** 22 * @var CloudSecureWP_Login_Log 23 */ 24 private $login_log; 25 26 function __construct( array $info, CloudSecureWP_Config $config, CloudSecureWP_Disable_Login $disable_login, CloudSecureWP_Login_Log $login_log ) { 23 27 parent::__construct( $info ); 24 28 $this->config = $config; 25 29 $this->disable_login = $disable_login; 30 $this->login_log = $login_log; 26 31 } 27 32 … … 135 140 private function is_role_enabled( $role ): bool { 136 141 return in_array( $role, get_option( 'cloudsecurewp_two_factor_authentication_roles', array() ) ); 137 }138 139 /**140 * ログインフォーム2段階認証チェック141 */142 public function wp_login( $user_login, $user ) {143 // 2段階認証が無効なとき144 if ( ! $this->is_enabled() ) {145 return;146 }147 148 if ( ! isset( $user->roles[0] ) ) {149 return;150 }151 152 // 有効な権限グループに含まれないとき153 if ( ! $this->is_role_enabled( $user->roles[0] ) ) {154 return;155 }156 157 $secret = get_user_option( 'cloudsecurewp_two_factor_authentication_secret', $user->ID );158 // ユーザーがデバイス登録をしていないとき159 if ( ! $secret ) {160 return;161 }162 163 // 初回ログイン時に元の認証情報を保存164 if ( empty( $this->original_credentials ) ) {165 $this->original_credentials['log'] = $user_login;166 $this->original_credentials['pwd'] = $_POST['pwd'] ?? '';167 }168 169 // 2段階認証コードが送られたとき170 if ( ! empty( $_POST['google_authenticator_code'] ) && check_admin_referer( $this->get_feature_key() . '_csrf' ) ) {171 $google_authenticator_code = sanitize_text_field( $_POST['google_authenticator_code'] );172 // 2段階認証コードが有効なとき173 if ( CloudSecureWP_Time_Based_One_Time_Password::verify_code( $secret, $google_authenticator_code, 2 ) ) {174 return;175 }176 // ログイン失敗回数をインクリメントしデータベースに格納177 $this->disable_login->wp_login_failed( $user_login );178 }179 180 wp_logout();181 login_header( '2段階認証画面' );182 $this->login_error();183 $this->login_form();184 login_footer();185 exit;186 142 } 187 143 … … 205 161 * 2段階認証のログインフォームを出力 206 162 * 207 * @return void 208 */ 209 private function login_form() { 163 * @param string $login_token 164 * 165 * @return void 166 */ 167 private function login_form( $login_token ) { 210 168 ?> 211 169 <form name="loginform" id="loginform" 212 170 action="<?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ); ?>" method="post"> 213 <input type="hidden" name="log" value="<?php echo base64_encode( $this->original_credentials['log'] ?? $_REQUEST['log'] ?? '' ); ?>"/>214 <input type="hidden" name="pwd" value="<?php echo base64_encode( $this->original_credentials['pwd'] ?? $_REQUEST['pwd'] ?? '' ); ?>"/>215 <?php if ( array_key_exists( 'cloudsecurewp_captcha', $_REQUEST ) ) : ?>216 <input type="hidden" name="cloudsecurewp_captcha"217 value="<?php echo esc_attr( sanitize_text_field( $_REQUEST['cloudsecurewp_captcha'] ) ); ?>"/>218 <?php endif; ?>219 <?php if ( array_key_exists( 'cloudsecurewp_captcha_prefix', $_REQUEST ) ) : ?>220 <input type="hidden" name="cloudsecurewp_captcha_prefix"221 value="<?php echo esc_attr( sanitize_text_field( $_REQUEST['cloudsecurewp_captcha_prefix'] ) ); ?>"/>222 <?php endif; ?>223 <?php if ( array_key_exists( 'cloudsecurewp_captcha_wpnonce', $_REQUEST ) ) : ?>224 <input type="hidden" name="cloudsecurewp_captcha_wpnonce"225 value="<?php echo esc_attr( sanitize_text_field( $_REQUEST['cloudsecurewp_captcha_wpnonce'] ) ); ?>"/>226 <?php endif; ?>227 171 <?php if ( array_key_exists( 'rememberme', $_REQUEST ) && 'forever' === sanitize_text_field( $_REQUEST['rememberme'] ) ) : ?> 228 172 <input name="rememberme" type="hidden" id="rememberme" value="forever"/> 229 173 <?php endif; ?> 174 <input type="hidden" name="login_token" value="<?php echo esc_attr( $login_token ); ?>"> 230 175 <p> 231 176 <label for="google_authenticator_code">認証コード</label> 232 177 <input type="text" name="google_authenticator_code" id="google_authenticator_code" class="input" 233 value="" size="20" />178 value="" size="20" autocomplete="one-time-code"/> 234 179 </p> 235 180 <script type="text/javascript">document.getElementById("google_authenticator_code").focus();</script> … … 296 241 297 242 /** 298 * 2段階認証フォームからのBase64エンコードされた認証情報をデコード 243 * ユーザの2faシークレットキー取得 244 * 245 * @param int $user_id 246 * 247 * @return mixed 248 */ 249 private function get_2fa_secret_key( int $user_id ) { 250 return get_user_option( 'cloudsecurewp_two_factor_authentication_secret', $user_id ); 251 } 252 253 /** 254 * option keyを作成 255 * 256 * @param string $token 257 * 258 * @return string 259 */ 260 private function create_option_key( string $token ): string { 261 return self::OPTION_PREFIX . $token; 262 } 263 264 /** 265 * option dataを登録 266 * 267 * @param string $key 268 * @param mixed $data 269 * 270 * @return void 271 */ 272 private function set_option_data( string $key, $data ): void { 273 update_option( $key, $data, false ); 274 } 275 276 /** 277 * option dataを取得 278 * 279 * @param string $key 280 * 281 * @return array|false データが存在しないまたは、有効期限切れの場合FALSEを返却 282 */ 283 private function get_option_data( string $key ) { 284 285 $data = get_option( $key ); 286 287 // データが存在しない 288 if ( ! $data || ! is_array( $data ) ) { 289 return false; 290 } 291 292 // 有効期限切れ 293 if ( ! isset( $data['expires'] ) || $data['expires'] <= time() ) { 294 return false; 295 } 296 297 // 有効なデータを返却 298 return $data; 299 } 300 301 /** 302 * option dataを削除 303 * 304 * @param string $key 305 * 306 * @return void 307 */ 308 private function delete_option_data( string $key ): void { 309 delete_option( $key ); 310 } 311 312 /** 313 * 2段階認証が必要かどうか判定処理 299 314 * 300 315 * @param mixed $user 316 * 317 * @return bool 318 */ 319 private function is_2fa_required( $user ): bool { 320 321 // 2段階認証が無効な場合 322 if ( ! $this->is_enabled() ) { 323 return false; 324 } 325 326 // 有効な権限グループに含まれない場合 327 if ( ! isset( $user->roles[0] ) || ! $this->is_role_enabled( $user->roles[0] ) ) { 328 return false; 329 } 330 331 // 2faシークレットキーが存在しない場合 332 if ( ! $this->get_2fa_secret_key( $user->ID ) ) { 333 return false; 334 } 335 336 return true; 337 } 338 339 /** 340 * 2段階認証画面を表示 341 * 342 * @param string $login_token 343 * 344 * @return void 345 */ 346 private function show_two_factor_form( string $login_token ) { 347 // 2FA画面を表示 348 login_header( '2段階認証画面' ); 349 $this->login_error(); 350 $this->login_form( $login_token ); 351 login_footer(); 352 exit; 353 } 354 355 /** 356 * POSTデータから2FA関連の値を安全に取得 357 * 358 * @return array 359 */ 360 private function get_2fa_post_data(): array { 361 return array( 362 'login_token' => sanitize_text_field( $_POST['login_token'] ?? '' ), 363 'google_authenticator_code' => sanitize_text_field( $_POST['google_authenticator_code'] ?? '' ), 364 ); 365 } 366 367 /** 368 * 2段階認証コード検証処理 369 * 370 * @param int $user_id 371 * @param string $code 372 * 373 * @return bool 374 */ 375 private function verify_2fa_code( int $user_id, string $code ): bool { 376 377 // 2faシークレットキー取得 378 $secret_key = $this->get_2fa_secret_key( $user_id ); 379 380 // 2faシークレットキーが存在しない場合 381 if ( ! $secret_key ) { 382 return true; 383 } 384 385 // 2段階認証コードが有効な場合 386 if ( CloudSecureWP_Time_Based_One_Time_Password::verify_code( $secret_key, $code, 2 ) ) { 387 return true; 388 } 389 390 // 認証失敗 391 return false; 392 } 393 394 /** 395 * 認証フック: ユーザ名復元処理 396 * 397 * @param string $username 398 * @param bool $strict 399 * 400 * @return string 401 */ 402 public function restore_login_name( string $username, $strict = false ): string { 403 404 // 初回アクセス・または初回認証の場合、何もしない 405 if ( ! isset( $_POST['google_authenticator_code'] ) ) { 406 return $username; 407 } 408 409 // 2FA関連のPOSTデータ取得 410 $post_data = $this->get_2fa_post_data(); 411 412 // ログイン情報を取得 413 $option_key = $this->create_option_key( $post_data['login_token'] ); 414 $option_data = $this->get_option_data( $option_key ); 415 416 // ログイン情報が存在しない場合、何もしない 417 if ( $option_data === false ) { 418 return $username; 419 } 420 421 // ユーザ名を返却 422 return $option_data['user_login']; 423 } 424 425 /** 426 * 認証フック: ログインデータ復元処理 427 * 428 * @param mixed $user 301 429 * @param string $username 302 430 * @param string $password 431 * 303 432 * @return mixed 304 433 */ 305 public function decode_base64_credentials( $user, $username, $password ) { 306 // 2段階認証フォームからの送信かチェック 307 if ( ! empty( $_POST['google_authenticator_code'] ) && check_admin_referer( $this->get_feature_key() . '_csrf' ) ) { 308 // Base64エンコードされた認証情報をデコード 309 if ( isset( $_POST['log'] ) ) { 310 $decoded_username = base64_decode( $_POST['log'] ); 311 $this->original_credentials['log'] = $decoded_username; 312 $_POST['log'] = $decoded_username; 313 } 314 315 if ( isset( $_POST['pwd'] ) ) { 316 $decoded_password = base64_decode( $_POST['pwd'] ); 317 $this->original_credentials['pwd'] = $decoded_password; 318 $_POST['pwd'] = $decoded_password; 319 320 // デコードされたパスワードで認証を実行 321 return wp_authenticate_username_password( null, $decoded_username, $decoded_password ); 322 } 323 } 324 434 public function restore_login_session( $user, $username, $password ) { 435 436 // 初回アクセス・または初回認証の場合、何もしない 437 if ( ! isset( $_POST['google_authenticator_code'] ) ) { 438 return $user; 439 } 440 441 // CSRFトークンを検証(失敗すると「辿ったリンクは期限が切れています。」のエラー画面を表示し処理終了) 442 check_admin_referer( $this->get_feature_key() . '_csrf' ); 443 444 // 2FA関連のPOSTデータ取得 445 $post_data = $this->get_2fa_post_data(); 446 447 // ログイン情報を取得 448 $option_key = $this->create_option_key( $post_data['login_token'] ); 449 $option_data = $this->get_option_data( $option_key ); 450 451 // ログインが有効期限切れの場合 452 // ログイン成功時にまとめてクリーンアップ処理を実行するため、ここでは消さない 453 if ( $option_data === false ) { 454 return new WP_Error( 'empty_username', 'セッションの有効期限が切れました。再度ログインしてください。' ); 455 } 456 457 // ユーザーオブジェクト取得 458 $user = get_user_by( 'id', $option_data['user_id'] ); 459 if ( ! $user ) { 460 $this->delete_option_data( $option_key ); 461 return new WP_Error( 'empty_username', 'ユーザー情報が見つかりません。再度ログインしてください。' ); 462 } 463 464 // ログイン情報のPOSTデータ復元 465 $_POST['log'] = $option_data['user_login']; 466 325 467 return $user; 326 468 } 469 470 /** 471 * 認証フック: 2段階認証処理 472 * 473 * @param mixed $user 474 * @param string $username 475 * @param string $password 476 * 477 * @return mixed 478 */ 479 public function authenticate_with_two_factor( $user, $username, $password ) { 480 481 // 初回アクセス、または初回認証時 482 if ( ! isset( $_POST['google_authenticator_code'] ) ) { 483 484 // 認証失敗の場合 485 if ( is_wp_error( $user ) ) { 486 return $user; 487 } 488 489 // 2段階認証が不要な場合 490 if ( ! $this->is_2fa_required( $user ) ) { 491 return $user; 492 } 493 494 // option key生成 495 $session_token = bin2hex( random_bytes( 16 ) ); 496 $option_key = $this->create_option_key( $session_token ); 497 498 // 保存用認証データ作成 499 $option_data = array( 500 'user_id' => $user->ID, 501 'user_login' => sanitize_text_field( $_POST['log'] ?? '' ), 502 'expires' => time() + self::SESSION_EXPIRY, 503 'created' => time(), 504 ); 505 506 // データを保存 507 $this->set_option_data( $option_key, $option_data ); 508 509 // 2FA画面を表示して、処理終了 510 $this->show_two_factor_form( $session_token ); 511 } 512 513 // 2FA関連のPOSTデータ取得 514 $post_data = $this->get_2fa_post_data(); 515 516 // option key取得 517 $option_key = $this->create_option_key( $post_data['login_token'] ); 518 519 // $userがWP_Errorの場合は処理をスキップ 520 if ( is_wp_error( $user ) ) { 521 return $user; 522 } 523 524 // 2段階認証成功の場合 525 if ( $this->verify_2fa_code( $user->ID, $post_data['google_authenticator_code'] ) ) { 526 $this->delete_option_data( $option_key ); 527 return $user; 528 } 529 530 // ログイン失敗時の処理を実行(ログイン回数・ログインログ) 531 do_action( 'wp_login_failed', $_POST['log'], $user ); 532 533 // 2FA画面を再表示して、処理終了 534 $this->show_two_factor_form( $post_data['login_token'] ); 535 } 536 537 /** 538 * 2FAセッションデータを取得 539 * 540 * @param int $last_option_id 541 * @param int $limit 542 * 543 * @return array 544 */ 545 private function fetch_2fa_sessions( int $last_option_id, int $limit ): array { 546 global $wpdb; 547 548 // セッションキーの接頭辞でLIKE検索 549 $like = $wpdb->esc_like( self::OPTION_PREFIX ) . '%'; 550 551 // SQL実行(LIKE検索を行うため、意図的に直接クエリを実行する) 552 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 553 $options = $wpdb->get_results( 554 $wpdb->prepare( 555 "SELECT 556 option_id, 557 option_name, 558 option_value 559 FROM 560 {$wpdb->options} 561 WHERE TRUE 562 AND option_name LIKE %s 563 AND %d < option_id 564 ORDER BY 565 option_id ASC 566 LIMIT 567 %d", 568 $like, 569 $last_option_id, 570 $limit 571 ) 572 ); 573 574 return $options ?? array(); 575 } 576 577 /** 578 * 期限切れセッションデータを収集 579 * 580 * @param array $options 581 * 582 * @return array ['log_data' => array, 'delete_option_names' => array] 583 */ 584 private function collect_expired_session_data( array $options ): array { 585 // ログ登録用データリスト初期化 586 $log_data = array(); 587 // 削除対象のoption_nameリスト 588 $delete_option_names = array(); 589 590 foreach ( $options as $option ) { 591 // option_valueを連想配列に変換 592 $data = maybe_unserialize( $option->option_value ); 593 594 // 配列でない場合はスキップ 595 if ( ! is_array( $data ) ) { 596 continue; 597 } 598 599 // 有効期限が切れている場合 600 if ( isset( $data['expires'] ) && $data['expires'] <= time() ) { 601 602 // ログ登録用データを収集 603 $log_data[] = array( 604 'name' => $data['user_login'], 605 'ip' => $this->get_client_ip( '' ), 606 'status' => self::LOGIN_STATUS_FAILED, 607 'method' => 1, 608 'login_at' => wp_date( 'Y-m-d H:i:s', $data['created'] ), // WPのタイムゾーンに変更して登録 609 ); 610 611 // 削除対象のoption_nameを収集 612 $delete_option_names[] = $option->option_name; 613 } 614 } 615 616 return array( 617 'log_data' => $log_data, 618 'delete_option_names' => $delete_option_names, 619 ); 620 } 621 622 /** 623 * ログイン失敗ログを一括登録 624 * (呼び出し元でトランザクションを管理すること) 625 * 626 * @param array $log_data 627 * 628 * @return void 629 * @throws Exception SQLエラー発生時. 630 */ 631 private function insert_login_failed_logs( array $log_data ): void { 632 if ( empty( $log_data ) ) { 633 return; 634 } 635 636 global $wpdb; 637 638 // プレースホルダーと値の準備 639 $values = array(); 640 $placeholders = array(); 641 642 // 収集したログデータを一括登録用に変換 643 foreach ( $log_data as $log ) { 644 $values[] = $log['name']; 645 $values[] = $log['ip']; 646 $values[] = $log['status']; 647 $values[] = $log['method']; 648 $values[] = $log['login_at']; 649 $placeholders[] = '(%s, %s, %d, %d, %s)'; 650 } 651 652 // SQL実行(一括登録を行うため、意図的に直接クエリを実行する) 653 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 654 $result = $wpdb->query( 655 $wpdb->prepare( 656 "INSERT INTO `{$wpdb->prefix}cloudsecurewp_login_log` 657 (`name`, `ip`, `status`, `method`, `login_at`) 658 VALUES " . implode( ', ', $placeholders ), 659 $values 660 ) 661 ); 662 663 // SQLエラーチェック 664 if ( $result === false || ! empty( $wpdb->last_error ) ) { 665 throw new Exception( 'Failed to insert login logs: ' . $wpdb->last_error ); 666 } 667 } 668 669 /** 670 * 指定されたオプションを一括削除 671 * (呼び出し元でトランザクションを管理すること) 672 * 673 * @param array $option_names 674 * 675 * @return void 676 * @throws Exception SQLエラー発生時. 677 */ 678 private function delete_options( array $option_names ): void { 679 if ( empty( $option_names ) ) { 680 return; 681 } 682 683 global $wpdb; 684 685 // プレースホルダー作成 686 $placeholders = implode( ', ', array_fill( 0, count( $option_names ), '%s' ) ); 687 688 // SQL実行(一括削除を行うため、意図的に直接クエリを実行する) 689 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 690 $result = $wpdb->query( 691 $wpdb->prepare( 692 "DELETE FROM 693 {$wpdb->options} 694 WHERE 695 option_name IN ($placeholders)", 696 $option_names 697 ) 698 ); 699 700 // SQLエラーチェック 701 if ( $result === false || ! empty( $wpdb->last_error ) ) { 702 throw new Exception( 'Failed to delete options: ' . $wpdb->last_error ); 703 } 704 } 705 706 /** 707 * 期限切れログイン情報セッションのクリーンアップ処理本体 708 * 709 * @return void 710 */ 711 private function process_cleanup_expired_sessions(): void { 712 global $wpdb; 713 714 $last_option_id = 0; 715 716 while ( true ) { 717 try { 718 // 2FAセッションデータを取得 719 $options = $this->fetch_2fa_sessions( $last_option_id, self::CLEANUP_BATCH_SIZE ); 720 721 // 取得するレコードがなくなったら終了 722 if ( empty( $options ) ) { 723 break; 724 } 725 726 // 最後に取得したoption_idを更新 727 $last_option_id = end( $options )->option_id; 728 729 // 期限切れセッションデータを収集 730 $result = $this->collect_expired_session_data( $options ); 731 $log_data = $result['log_data']; 732 $delete_option_names = $result['delete_option_names']; 733 734 if ( empty( $delete_option_names ) && empty( $log_data ) ) { 735 continue; 736 } 737 738 // トランザクション開始 739 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 740 $wpdb->query( 'START TRANSACTION' ); 741 742 // ログデータを一括登録 743 $this->insert_login_failed_logs( $log_data ); 744 745 // オプションを一括削除 746 $this->delete_options( $delete_option_names ); 747 748 // トランザクションコミット 749 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 750 $wpdb->query( 'COMMIT' ); 751 752 } catch ( Exception $e ) { 753 // エラー発生時はロールバック 754 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 755 $wpdb->query( 'ROLLBACK' ); 756 break; 757 } 758 } 759 } 760 761 /** 762 * 期限切れの2FAセッションをクリーンアップ 763 * 764 * @return void 765 */ 766 public function cleanup_expired_sessions(): void { 767 global $wpdb; 768 769 // ロック名 770 $lock_name = 'cloudsecurewp_2fa_cleanup_lock'; 771 // クリーンアップ処理の完了を待つ最大秒数 772 $timeout = self::CLEANUP_TIMEOUT; 773 774 // ロックを取得 775 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 776 $get_lock = $wpdb->get_var( 777 $wpdb->prepare( 'SELECT GET_LOCK(%s, 0)', $lock_name ) 778 ); 779 780 if ( $get_lock === '1' ) { 781 // ロック取得成功(クリーンアップ実行者) 782 783 try { 784 // クリーンアップ処理実行 785 $this->process_cleanup_expired_sessions(); 786 } catch ( Exception $e ) { 787 // クリーンアップ処理実行で失敗しても内部でロールバックするため、ここでは何もしない 788 } finally { 789 // ロック解放 790 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 791 $wpdb->query( 792 $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock_name ) 793 ); 794 } 795 } else { 796 // ロック取得失敗(待機者) 797 798 // リーダーのクリーンアップ完了を待機 799 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 800 $acquired_signal = $wpdb->get_var( 801 $wpdb->prepare( 'SELECT GET_LOCK(%s, %d)', $lock_name, $timeout ) 802 ); 803 804 if ( $acquired_signal === '1' ) { 805 // ロック取得成功(クリーンアップ処理終了) 806 // 即座にロック解放(待機完了の合図として使うだけ) 807 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 808 $wpdb->query( 809 $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock_name ) 810 ); 811 } 812 } 813 } 327 814 } -
cloudsecure-wp-security/trunk/readme.txt
r3408912 r3422588 4 4 Requires at least: 5.3.15 5 5 Tested up to: 6.9 6 Stable tag: 1.3.2 16 Stable tag: 1.3.22 7 7 License: GPLv2 or later 8 8 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 107 107 == Changelog == 108 108 109 = 1.3.22 = 110 * 2段階認証に関する軽微な修正 111 109 112 = 1.3.21 = 110 113 * WordPress6.9をサポート
Note: See TracChangeset
for help on using the changeset viewer.