PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.4.9
CloudSecure WP Security v1.4.9
1.4.10 1.4.9 trunk 0.9.0 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.19 1.3.2 1.3.20 1.3.21 1.3.22 1.3.23 1.3.24 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8
cloudsecure-wp-security / modules / lib / class-recovery-codes.php
cloudsecure-wp-security / modules / lib Last commit date
class-disable-access-system-file-rules.php 11 months ago class-recovery-codes.php 3 months ago class-time-based-one-time-password.php 4 months ago class-waf-rules.php 1 year ago
class-recovery-codes.php
213 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * リカバリーコード管理クラス
9 * 2段階認証のバックアップ用コードを生成・管理
10 */
11 class CloudSecureWP_Recovery_Codes {
12 private const CODE_LENGTH = 12;
13 private const CODE_COUNT = 10;
14
15 /**
16 * 単一のリカバリーコードを生成
17 *
18 * @return string
19 */
20 private static function create_single_code(): string {
21 // 12文字の英数字コードを生成(大文字・小文字両方含む、紛らわしい文字「01oOlI」を除外)
22 $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
23 $code = '';
24
25 for ( $i = 0; $i < self::CODE_LENGTH; $i++ ) {
26 $code .= $chars[ random_int( 0, strlen( $chars ) - 1 ) ];
27 }
28
29 return $code;
30 }
31
32 /**
33 * リカバリーコードを生成
34 *
35 * @return array ['plain_codes' => array, 'hashed_codes' => array]
36 */
37 private static function create_code(): array {
38 $plain_codes = array();
39 $hashed_codes = array();
40
41 for ( $i = 0; $i < self::CODE_COUNT; $i++ ) {
42 // コードを生成
43 $code = self::create_single_code();
44
45 // 平文コードを�
46 �列に追加(表示用、ハイフン付き)
47 $plain_codes[] = substr( $code, 0, 4 ) . '-' . substr( $code, 4, 4 ) . '-' . substr( $code, 8, 4 );
48
49 // ハッシュ化したコードを�
50 �列に追加(保存用)
51 $hashed_codes[] = wp_hash_password( $code );
52 }
53
54 return array(
55 'plain_codes' => $plain_codes,
56 'hashed_codes' => $hashed_codes,
57 );
58 }
59
60 /**
61 * ユーザーのリカバリーコード(ハッシュ)を保存
62 *
63 * @param int $user_id
64 * @param array $hashed_codes
65 *
66 * @return bool true: 保存成功、false: 保存失敗
67 */
68 private static function save_codes( int $user_id, array $hashed_codes ): bool {
69 global $wpdb;
70 $table_name = $wpdb->prefix . 'cloudsecurewp_2fa_auth';
71
72 // �
73 �列をJSON形式にエンコード
74 $json_codes = wp_json_encode( $hashed_codes );
75
76 // レコードが存在するかチェック
77 $sql = "SELECT 1 FROM $table_name WHERE user_id = %d";
78 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
79 $exists = $wpdb->get_var( $wpdb->prepare( $sql, $user_id ) );
80
81 if ( $exists ) {
82 // 更新
83 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
84 $result = $wpdb->update(
85 $table_name,
86 array( 'recovery' => $json_codes ),
87 array( 'user_id' => $user_id ),
88 array( '%s' ),
89 array( '%d' )
90 );
91 } else {
92 // ここには�
93 �らない想定
94 return false;
95 }
96
97 return $result !== false;
98 }
99
100 /**
101 * リカバリーコードを初期化
102 *
103 * @param int $user_id
104 *
105 * @return array 平文コードの�
106 �列(生成・保存に失敗した場合は空�
107 �列を返却)
108 */
109 public static function initialize_codes( int $user_id ): array {
110 // 新しいコードを生成
111 $recovery_code = self::create_code();
112 if ( count( $recovery_code['plain_codes'] ) === 0 || count( $recovery_code['hashed_codes'] ) === 0 ) {
113 return [];
114 }
115
116 // ハッシュ化されたコードをDBに保存(既存のコードは上書きされる)
117 $result = self::save_codes( $user_id, $recovery_code['hashed_codes'] );
118 if ( ! $result ) {
119 return [];
120 }
121
122 // 平文コードを返却
123 return $recovery_code['plain_codes'];
124 }
125
126 /**
127 * ユーザーのリカバリーコードを取得
128 *
129 * @param int $user_id
130 *
131 * @return array|false
132 */
133 private static function get_codes( int $user_id ) {
134 global $wpdb;
135
136 // DBからコードを取得
137 $sql = "SELECT recovery FROM {$wpdb->prefix}cloudsecurewp_2fa_auth WHERE user_id = %d";
138 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
139 $stored_value = $wpdb->get_var( $wpdb->prepare( $sql, $user_id ) );
140 if ( ! $stored_value ) {
141 return false;
142 }
143
144 // JSON形式を優�
145 �でデコード(新形式)
146 $stored_codes = json_decode( $stored_value, true );
147 if ( is_array( $stored_codes ) ) {
148 return $stored_codes;
149 }
150
151 // フォールバック:serialize形式をデコード(旧形式)
152 if ( ! is_serialized( $stored_value ) ) {
153 return false;
154 }
155 $stored_codes = unserialize( $stored_value, array( 'allowed_classes' => false ) );
156 if ( ! $stored_codes || ! is_array( $stored_codes ) ) {
157 return false;
158 }
159
160 return $stored_codes;
161 }
162
163 /**
164 * コードを正規化(ハイフンとスペースを除去)
165 *
166 * @param string $code
167 *
168 * @return string
169 */
170 private static function normalize_code( string $code ): string {
171 // 両端の空白を除去し、間のハイフンを除去
172 $code = trim( $code );
173 $code = preg_replace( '/(?<!^)-(?!$)/', '', $code );
174 return $code;
175 }
176
177 /**
178 * リカバリーコードを検証
179 *
180 * @param int $user_id
181 * @param string $code
182 *
183 * @return bool true: 検証成功、false: 検証失敗
184 */
185 public static function verify_code( int $user_id, string $code ): bool {
186 // DBからコードを取得
187 $stored_codes = self::get_codes( $user_id );
188 if ( ! is_array( $stored_codes ) || count( $stored_codes ) === 0 ) {
189 return false;
190 }
191
192 // �
193 �力コードを正規化
194 $normalized_code = self::normalize_code( $code );
195
196 // 保存されているコードと�
197 �合
198 foreach ( $stored_codes as $index => $stored_code ) {
199 // コードを検証
200 if ( wp_check_password( $normalized_code, $stored_code ) ) {
201 // コードを使用済みとして削除
202 unset( $stored_codes[ $index ] );
203 // DBを更新(array_values でインデックスを再構築し、JSON�
204 �列形式を維持する)
205 self::save_codes( $user_id, array_values( $stored_codes ) );
206 return true;
207 }
208 }
209
210 return false;
211 }
212 }
213