PluginProbe
Atomic Edge Security – Firewall, Malware Scan and Login Security / 2.2.2
Atomic Edge Security – Firewall, Malware Scan and Login Security v2.2.2
trunk 2.0.0 2.1.0 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 All 29 releases
atomic-edge-security / admin / views / 2fa-users.php

2fa-users.php in Atomic Edge Security – Firewall, Malware Scan and Login Security 2.2.2, at admin/views/2fa-users.php

320 lines 12.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * 2FA User Management Admin View
4 *
5 * Allows admins to view and manage user 2FA status.
6 *
7 * @package AtomicEdge
8 * @since 1.8.0
9 */
10
11 // Prevent direct access.
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 // Handle admin reset action.
17 if ( isset( $_POST['atomicedge_reset_2fa'] ) && isset( $_POST['reset_user_id'] ) ) {
18 // Verify nonce.
19 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ?? '' ) ), 'atomicedge_reset_2fa' ) ) {
20 wp_die( esc_html__( 'Security check failed.', 'atomicedge' ) );
21 }
22
23 // Check capability.
24 if ( ! current_user_can( 'manage_options' ) ) {
25 wp_die( esc_html__( 'You do not have permission to perform this action.', 'atomicedge' ) );
26 }
27
28 $reset_user_id = absint( $_POST['reset_user_id'] );
29 $reset_user = get_userdata( $reset_user_id );
30
31 if ( $reset_user && AtomicEdge_2FA::is_enabled_for_user( $reset_user_id ) ) {
32 // Disable 2FA for the user.
33 AtomicEdge_2FA::disable( $reset_user_id );
34
35 // Clear policy grace period.
36 AtomicEdge_2FA_Policy::end_grace_period( $reset_user_id );
37
38 // Log the admin reset.
39 do_action( 'atomicedge_2fa_event', $reset_user_id, 'admin_reset', array(
40 'admin_id' => get_current_user_id(),
41 'reason' => 'Admin manually reset 2FA',
42 ) );
43
44 $success_message = sprintf(
45 /* translators: %s: username */
46 __( '2FA has been disabled for user "%s". They will need to set up 2FA again.', 'atomicedge' ),
47 $reset_user->user_login
48 );
49 }
50 }
51
52 // Get search/filter.
53 $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
54 $filter = isset( $_GET['filter'] ) ? sanitize_text_field( wp_unslash( $_GET['filter'] ) ) : '';
55
56 // Build user query.
57 $args = array(
58 'number' => 50,
59 'orderby' => 'display_name',
60 'order' => 'ASC',
61 );
62
63 if ( $search ) {
64 $args['search'] = '*' . $search . '*';
65 $args['search_columns'] = array( 'user_login', 'user_email', 'display_name' );
66 }
67
68 $users = get_users( $args );
69
70 // Filter users.
71 $filtered_users = array();
72 foreach ( $users as $user ) {
73 $has_2fa = AtomicEdge_2FA::is_enabled_for_user( $user->ID );
74
75 if ( 'enabled' === $filter && ! $has_2fa ) {
76 continue;
77 }
78 if ( 'disabled' === $filter && $has_2fa ) {
79 continue;
80 }
81
82 $filtered_users[] = $user;
83 }
84
85 // Count 2FA stats.
86 $total_users = count_users();
87 $users_with_2fa = 0;
88 $all_user_ids = get_users( array( 'fields' => 'ID' ) );
89 foreach ( $all_user_ids as $uid ) {
90 if ( AtomicEdge_2FA::is_enabled_for_user( $uid ) ) {
91 $users_with_2fa++;
92 }
93 }
94 $users_without_2fa = $total_users['total_users'] - $users_with_2fa;
95 ?>
96 <div class="wrap">
97 <h1><?php echo esc_html__( '2FA User Management', 'atomicedge' ); ?></h1>
98
99 <p class="description">
100 <?php echo esc_html__( 'View and manage Two-Factor Authentication status for all users.', 'atomicedge' ); ?>
101 </p>
102
103 <?php if ( ! empty( $success_message ) ) : ?>
104 <div class="notice notice-success is-dismissible">
105 <p><?php echo esc_html( $success_message ); ?></p>
106 </div>
107 <?php endif; ?>
108
109 <!-- Statistics -->
110 <div class="atomicedge-stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin: 20px 0; max-width: 600px;">
111 <div class="atomicedge-stat-card" style="background: #fff; padding: 20px; border-left: 4px solid #0073aa; box-shadow: 0 1px 1px rgba(0,0,0,.04);">
112 <div class="stat-value" style="font-size: 28px; font-weight: 600; color: #23282d;">
113 <?php echo esc_html( $total_users['total_users'] ); ?>
114 </div>
115 <div class="stat-label" style="color: #666; font-size: 13px;"><?php echo esc_html__( 'Total Users', 'atomicedge' ); ?></div>
116 </div>
117 <div class="atomicedge-stat-card" style="background: #fff; padding: 20px; border-left: 4px solid #46b450; box-shadow: 0 1px 1px rgba(0,0,0,.04);">
118 <div class="stat-value" style="font-size: 28px; font-weight: 600; color: #46b450;">
119 <?php echo esc_html( $users_with_2fa ); ?>
120 </div>
121 <div class="stat-label" style="color: #666; font-size: 13px;"><?php echo esc_html__( '2FA Enabled', 'atomicedge' ); ?></div>
122 </div>
123 <div class="atomicedge-stat-card" style="background: #fff; padding: 20px; border-left: 4px solid #f0ad4e; box-shadow: 0 1px 1px rgba(0,0,0,.04);">
124 <div class="stat-value" style="font-size: 28px; font-weight: 600; color: #f0ad4e;">
125 <?php echo esc_html( $users_without_2fa ); ?>
126 </div>
127 <div class="stat-label" style="color: #666; font-size: 13px;"><?php echo esc_html__( 'No 2FA', 'atomicedge' ); ?></div>
128 </div>
129 </div>
130
131 <!-- Search and Filter -->
132 <form method="get" action="" style="margin-bottom: 20px;">
133 <input type="hidden" name="page" value="atomicedge-2fa-users" />
134
135 <input type="search" name="s" value="<?php echo esc_attr( $search ); ?>"
136 placeholder="<?php echo esc_attr__( 'Search users...', 'atomicedge' ); ?>" style="min-width: 200px;" />
137
138 <select name="filter">
139 <option value=""><?php echo esc_html__( 'All Users', 'atomicedge' ); ?></option>
140 <option value="enabled" <?php selected( $filter, 'enabled' ); ?>><?php echo esc_html__( '2FA Enabled', 'atomicedge' ); ?></option>
141 <option value="disabled" <?php selected( $filter, 'disabled' ); ?>><?php echo esc_html__( '2FA Disabled', 'atomicedge' ); ?></option>
142 </select>
143
144 <button type="submit" class="button"><?php echo esc_html__( 'Search', 'atomicedge' ); ?></button>
145
146 <?php if ( $search || $filter ) : ?>
147 <a href="<?php echo esc_url( admin_url( 'admin.php?page=atomicedge-2fa-users' ) ); ?>" class="button">
148 <?php echo esc_html__( 'Clear', 'atomicedge' ); ?>
149 </a>
150 <?php endif; ?>
151 </form>
152
153 <!-- Users Table -->
154 <table class="wp-list-table widefat fixed striped users">
155 <thead>
156 <tr>
157 <th scope="col" class="manage-column column-username column-primary" style="width: 200px;">
158 <?php echo esc_html__( 'Username', 'atomicedge' ); ?>
159 </th>
160 <th scope="col" class="manage-column"><?php echo esc_html__( 'Name', 'atomicedge' ); ?></th>
161 <th scope="col" class="manage-column" style="width: 200px;"><?php echo esc_html__( 'Email', 'atomicedge' ); ?></th>
162 <th scope="col" class="manage-column" style="width: 120px;"><?php echo esc_html__( 'Role', 'atomicedge' ); ?></th>
163 <th scope="col" class="manage-column" style="width: 120px;"><?php echo esc_html__( '2FA Status', 'atomicedge' ); ?></th>
164 <th scope="col" class="manage-column" style="width: 120px;"><?php echo esc_html__( 'Actions', 'atomicedge' ); ?></th>
165 </tr>
166 </thead>
167 <tbody>
168 <?php if ( empty( $filtered_users ) ) : ?>
169 <tr>
170 <td colspan="6" style="text-align: center; padding: 20px;">
171 <?php echo esc_html__( 'No users found.', 'atomicedge' ); ?>
172 </td>
173 </tr>
174 <?php else : ?>
175 <?php foreach ( $filtered_users as $user ) : ?>
176 <?php
177 $has_2fa = AtomicEdge_2FA::is_enabled_for_user( $user->ID );
178 $backup_count = 0;
179 $policy_status = AtomicEdge_2FA_Policy::get_user_enforcement_status( $user );
180
181 if ( $has_2fa ) {
182 $backup_codes = get_user_meta( $user->ID, AtomicEdge_2FA_Backup::META_KEY, true );
183 if ( is_array( $backup_codes ) ) {
184 $backup_count = count( $backup_codes );
185 }
186 }
187 ?>
188 <tr>
189 <td class="column-username column-primary" data-colname="<?php echo esc_attr__( 'Username', 'atomicedge' ); ?>">
190 <strong>
191 <a href="<?php echo esc_url( admin_url( 'user-edit.php?user_id=' . $user->ID ) ); ?>">
192 <?php echo esc_html( $user->user_login ); ?>
193 </a>
194 </strong>
195 </td>
196 <td data-colname="<?php echo esc_attr__( 'Name', 'atomicedge' ); ?>">
197 <?php echo esc_html( $user->display_name ); ?>
198 </td>
199 <td data-colname="<?php echo esc_attr__( 'Email', 'atomicedge' ); ?>">
200 <a href="mailto:<?php echo esc_attr( $user->user_email ); ?>">
201 <?php echo esc_html( $user->user_email ); ?>
202 </a>
203 </td>
204 <td data-colname="<?php echo esc_attr__( 'Role', 'atomicedge' ); ?>">
205 <?php
206 $roles = array_map( function ( $role ) {
207 $role_obj = get_role( $role );
208 return $role_obj ? translate_user_role( ucfirst( $role ) ) : ucfirst( $role );
209 }, $user->roles );
210 echo esc_html( implode( ', ', $roles ) );
211 ?>
212 </td>
213 <td data-colname="<?php echo esc_attr__( '2FA Status', 'atomicedge' ); ?>">
214 <?php if ( $has_2fa ) : ?>
215 <span style="color: #46b450; font-weight: 500;">
216 <span class="dashicons dashicons-shield-alt" style="font-size: 16px; width: 16px; height: 16px; vertical-align: text-bottom;"></span>
217 <?php echo esc_html__( 'Enabled', 'atomicedge' ); ?>
218 </span>
219 <?php if ( $backup_count > 0 ) : ?>
220 <br>
221 <small style="color: #666;">
222 <?php
223 printf(
224 /* translators: %d: number of backup codes */
225 esc_html( _n( '%d backup code', '%d backup codes', $backup_count, 'atomicedge' ) ),
226 esc_html( $backup_count )
227 );
228 ?>
229 </small>
230 <?php endif; ?>
231 <?php else : ?>
232 <span style="color: #999;">
233 <?php echo esc_html__( 'Disabled', 'atomicedge' ); ?>
234 </span>
235 <?php if ( 'required' === $policy_status['status'] || 'grace_period' === $policy_status['status'] ) : ?>
236 <br>
237 <small style="color: #dc3232;">
238 <?php
239 if ( 'grace_period' === $policy_status['status'] ) {
240 printf(
241 /* translators: %d: number of days remaining */
242 esc_html( _n( '%d day to set up', '%d days to set up', $policy_status['days_remaining'], 'atomicedge' ) ),
243 esc_html( $policy_status['days_remaining'] )
244 );
245 } else {
246 echo esc_html__( 'Required by policy', 'atomicedge' );
247 }
248 ?>
249 </small>
250 <?php endif; ?>
251 <?php endif; ?>
252 </td>
253 <td data-colname="<?php echo esc_attr__( 'Actions', 'atomicedge' ); ?>">
254 <?php if ( $has_2fa ) : ?>
255 <button type="button" class="button button-small atomicedge-reset-2fa"
256 data-user-id="<?php echo esc_attr( $user->ID ); ?>"
257 data-username="<?php echo esc_attr( $user->user_login ); ?>">
258 <?php echo esc_html__( 'Reset', 'atomicedge' ); ?>
259 </button>
260 <?php else : ?>
261 <span style="color: #999;"></span>
262 <?php endif; ?>
263 </td>
264 </tr>
265 <?php endforeach; ?>
266 <?php endif; ?>
267 </tbody>
268 </table>
269
270 <!-- Reset Confirmation Modal -->
271 <div id="atomicedge-reset-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 100000;">
272 <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; padding: 30px; border-radius: 4px; max-width: 400px; box-shadow: 0 3px 30px rgba(0,0,0,0.3);">
273 <h2 style="margin-top: 0;"><?php echo esc_html__( 'Reset 2FA?', 'atomicedge' ); ?></h2>
274 <p>
275 <?php echo esc_html__( 'Are you sure you want to reset Two-Factor Authentication for', 'atomicedge' ); ?>
276 <strong id="reset-username"></strong>?
277 </p>
278 <p style="color: #dc3232;">
279 <?php echo esc_html__( 'This will disable their 2FA and they will need to set it up again. Use this only if the user is locked out.', 'atomicedge' ); ?>
280 </p>
281 <form method="post" action="" id="reset-2fa-form">
282 <?php wp_nonce_field( 'atomicedge_reset_2fa' ); ?>
283 <input type="hidden" name="reset_user_id" id="reset-user-id" value="" />
284 <input type="hidden" name="atomicedge_reset_2fa" value="1" />
285 <p style="margin-bottom: 0;">
286 <button type="submit" class="button button-primary" style="background: #dc3232; border-color: #dc3232;">
287 <?php echo esc_html__( 'Yes, Reset 2FA', 'atomicedge' ); ?>
288 </button>
289 <button type="button" class="button" id="cancel-reset">
290 <?php echo esc_html__( 'Cancel', 'atomicedge' ); ?>
291 </button>
292 </p>
293 </form>
294 </div>
295 </div>
296
297 <script type="text/javascript">
298 (function($) {
299 $('.atomicedge-reset-2fa').on('click', function() {
300 var userId = $(this).data('user-id');
301 var username = $(this).data('username');
302 $('#reset-user-id').val(userId);
303 $('#reset-username').text(username);
304 $('#atomicedge-reset-modal').show();
305 });
306
307 $('#cancel-reset, #atomicedge-reset-modal').on('click', function(e) {
308 if (e.target === this) {
309 $('#atomicedge-reset-modal').hide();
310 }
311 });
312
313 // Prevent modal close when clicking inside modal content.
314 $('#atomicedge-reset-modal > div').on('click', function(e) {
315 e.stopPropagation();
316 });
317 })(jQuery);
318 </script>
319 </div>
320