init_hooks(); } /** * Initialize WordPress hooks. * * @since 0.1.0 */ private function init_hooks(): void { add_action( 'rest_api_init', array( $this, 'register_routes' ) ); } /** * Register REST API routes. * * @since 0.1.0 */ public function register_routes(): void { // GET /two-factor-extended/v1/status. register_rest_route( self::NAMESPACE, '/status', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_status' ), 'permission_callback' => array( $this, 'check_manage_options_permission' ), 'args' => array( 'role' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => '', ), ), ) ); // GET /two-factor-extended/v1/users. register_rest_route( self::NAMESPACE, '/users', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_users' ), 'permission_callback' => array( $this, 'check_manage_options_permission' ), 'args' => array( 'role' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => '', ), 'non_compliant_only' => array( 'type' => 'boolean', 'default' => false, ), ), ) ); // POST /two-factor-extended/v1/enforce. register_rest_route( self::NAMESPACE, '/enforce', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'enforce_requirements' ), 'permission_callback' => array( $this, 'check_manage_options_permission' ), 'args' => array( 'user_ids' => array( 'type' => 'array', 'required' => true, 'items' => array( 'type' => 'integer', ), ), 'reset_grace' => array( 'type' => 'boolean', 'default' => false, ), ), ) ); // POST /two-factor-extended/v1/reset. register_rest_route( self::NAMESPACE, '/reset', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'reset_grace_period' ), 'permission_callback' => array( $this, 'check_manage_options_permission' ), 'args' => array( 'user_ids' => array( 'type' => 'array', 'required' => true, 'items' => array( 'type' => 'integer', ), ), ), ) ); // GET /two-factor-extended/v1/report. register_rest_route( self::NAMESPACE, '/report', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_report' ), 'permission_callback' => array( $this, 'check_manage_options_permission' ), 'args' => array( 'role' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'default' => '', ), 'format' => array( 'type' => 'string', 'enum' => array( 'json', 'csv' ), 'default' => 'json', ), ), ) ); } /** * Check if user has manage_options permission. * * @since 0.1.0 * * @return bool True if user has permission. */ public function check_manage_options_permission(): bool { return current_user_can( 'manage_options' ); } /** * Get compliance status. * * @since 0.1.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response Response object. */ public function get_status( WP_REST_Request $request ): WP_REST_Response { try { $role = $request->get_param( 'role' ); $compliance = two_factor_extended()->get_compliance_report(); if ( null === $compliance ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Compliance report module is not available.', 'two-factor-extended' ), ), 500 ); } $stats = $compliance->get_compliance_stats( array( 'role' => is_string( $role ) ? $role : '' ) ); return new WP_REST_Response( array( 'success' => true, 'data' => $stats, ), 200 ); } catch ( Exception $e ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Unable to retrieve compliance status.', 'two-factor-extended' ), ), 500 ); } } /** * Get users list. * * @since 0.1.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response Response object. */ public function get_users( WP_REST_Request $request ): WP_REST_Response { $role = $request->get_param( 'role' ); $non_compliant_only = $request->get_param( 'non_compliant_only' ); $role_string = is_string( $role ) ? $role : ''; $compliance = two_factor_extended()->get_compliance_report(); if ( null === $compliance ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Compliance report module is not available.', 'two-factor-extended' ), ), 500 ); } if ( $non_compliant_only ) { $users = $compliance->get_non_compliant_users( array( 'role' => $role_string ) ); } else { // Get all users with compliance status. $user_args = array( 'fields' => 'all' ); if ( ! empty( $role_string ) ) { $user_args['role'] = $role_string; } $all_users = get_users( $user_args ); $enforcement = two_factor_extended()->get_enforcement(); $users = array(); if ( null === $enforcement ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Enforcement module is not available.', 'two-factor-extended' ), ), 500 ); } foreach ( $all_users as $user ) { $required = $enforcement->get_required_providers_for_user( $user->ID ); $users[] = array( 'user_id' => $user->ID, 'user_login' => $user->user_login, 'user_email' => $user->user_email, 'roles' => Two_Factor_Extended_Role_Manager::get_user_roles( $user->ID ), 'compliant' => empty( $required ) || $enforcement->user_meets_requirements( $user->ID, $required ), 'in_grace' => $enforcement->is_in_grace_period( $user->ID ), 'grace_remaining' => $enforcement->get_grace_period_remaining_days( $user->ID ), ); } } return new WP_REST_Response( array( 'success' => true, 'data' => $users, 'total' => count( $users ), ), 200 ); } /** * Enforce 2FA requirements. * * @since 0.1.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response Response object. */ public function enforce_requirements( WP_REST_Request $request ): WP_REST_Response { $user_ids = $request->get_param( 'user_ids' ); $reset_grace = $request->get_param( 'reset_grace' ); if ( ! is_array( $user_ids ) ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Invalid user_ids parameter.', 'two-factor-extended' ), ), 400 ); } $enforcement = two_factor_extended()->get_enforcement(); if ( null === $enforcement ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Enforcement module is not available.', 'two-factor-extended' ), ), 500 ); } $processed = 0; $errors = array(); foreach ( $user_ids as $user_id ) { if ( ! is_numeric( $user_id ) ) { $errors[] = 'Invalid user_id value provided.'; continue; } $user_id_int = (int) $user_id; $user = get_userdata( $user_id_int ); if ( ! $user ) { $errors[] = sprintf( 'User not found: %d', $user_id_int ); continue; } $required = $enforcement->get_required_providers_for_user( $user_id_int ); if ( empty( $required ) ) { $errors[] = sprintf( 'No 2FA requirements for user: %s', $user->user_login ); continue; } // Set enforcement start date. update_user_meta( $user_id_int, Two_Factor_Extended_Enforcement::META_ENFORCEMENT_START, time() ); if ( $reset_grace ) { delete_user_meta( $user_id_int, Two_Factor_Extended_Enforcement::META_GRACE_NOTIFIED ); } // Log the action. $audit_log = two_factor_extended()->get_audit_log(); if ( null !== $audit_log ) { $audit_log->log_event( 'api_enforce', sprintf( 'REST API: Enforced 2FA for user %s', $user->user_login ), $user_id_int, array( 'required_providers' => $required ) ); } ++$processed; } return new WP_REST_Response( array( 'success' => true, 'processed' => $processed, 'errors' => $errors, ), 200 ); } /** * Reset grace period. * * @since 0.1.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response Response object. */ public function reset_grace_period( WP_REST_Request $request ): WP_REST_Response { $user_ids = $request->get_param( 'user_ids' ); $processed = 0; $errors = array(); if ( ! is_array( $user_ids ) ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Invalid user_ids parameter.', 'two-factor-extended' ), ), 400 ); } foreach ( $user_ids as $user_id ) { if ( ! is_numeric( $user_id ) ) { $errors[] = 'Invalid user_id value provided.'; continue; } $user_id_int = (int) $user_id; $user = get_userdata( $user_id_int ); if ( ! $user ) { $errors[] = sprintf( 'User not found: %d', $user_id_int ); continue; } // Reset enforcement start date. update_user_meta( $user_id_int, Two_Factor_Extended_Enforcement::META_ENFORCEMENT_START, time() ); // Clear grace period notified flag. delete_user_meta( $user_id_int, Two_Factor_Extended_Enforcement::META_GRACE_NOTIFIED ); // Log the action. $audit_log = two_factor_extended()->get_audit_log(); if ( null !== $audit_log ) { $audit_log->log_event( 'api_reset_grace', sprintf( 'REST API: Reset grace period for user %s', $user->user_login ), $user_id_int ); } ++$processed; } return new WP_REST_Response( array( 'success' => true, 'processed' => $processed, 'errors' => $errors, ), 200 ); } /** * Get compliance report. * * @since 0.1.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response Response object. */ public function get_report( WP_REST_Request $request ): WP_REST_Response { $role = $request->get_param( 'role' ); $format = $request->get_param( 'format' ); $role_string = is_string( $role ) ? $role : ''; $compliance = two_factor_extended()->get_compliance_report(); if ( null === $compliance ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Compliance report module is not available.', 'two-factor-extended' ), ), 500 ); } if ( 'csv' === $format ) { $csv = $compliance->export_to_csv( array( 'role' => $role_string ) ); return new WP_REST_Response( array( 'success' => true, 'data' => $csv, 'format' => 'csv', ), 200, array( 'Content-Type' => 'text/csv', ) ); } // JSON format. $non_compliant = $compliance->get_non_compliant_users( array( 'role' => $role_string ) ); $stats = $compliance->get_compliance_stats( array( 'role' => $role_string ) ); return new WP_REST_Response( array( 'success' => true, 'statistics' => $stats, 'non_compliant' => $non_compliant, ), 200 ); } }