360 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * Class for registering & modifying FIDO U2F security keys.
 | 
						|
 *
 | 
						|
 * @package Two_Factor
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * Class for registering & modifying FIDO U2F security keys.
 | 
						|
 *
 | 
						|
 * @since 0.1-dev
 | 
						|
 *
 | 
						|
 * @package Two_Factor
 | 
						|
 */
 | 
						|
class Two_Factor_FIDO_U2F_Admin {
 | 
						|
 | 
						|
	/**
 | 
						|
	 * The user meta register data.
 | 
						|
	 *
 | 
						|
	 * @type string
 | 
						|
	 */
 | 
						|
	const REGISTER_DATA_USER_META_KEY = '_two_factor_fido_u2f_register_request';
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Add various hooks.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 */
 | 
						|
	public static function add_hooks() {
 | 
						|
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
 | 
						|
		add_action( 'show_user_security_settings', array( __CLASS__, 'show_user_profile' ) );
 | 
						|
		add_action( 'personal_options_update', array( __CLASS__, 'catch_submission' ), 0 );
 | 
						|
		add_action( 'edit_user_profile_update', array( __CLASS__, 'catch_submission' ), 0 );
 | 
						|
		add_action( 'load-profile.php', array( __CLASS__, 'catch_delete_security_key' ) );
 | 
						|
		add_action( 'load-user-edit.php', array( __CLASS__, 'catch_delete_security_key' ) );
 | 
						|
		add_action( 'wp_ajax_inline-save-key', array( __CLASS__, 'wp_ajax_inline_save' ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Enqueue assets.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 *
 | 
						|
	 * @param string $hook Current page.
 | 
						|
	 */
 | 
						|
	public static function enqueue_assets( $hook ) {
 | 
						|
		if ( ! in_array( $hook, array( 'user-edit.php', 'profile.php' ), true ) ) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$user_id = Two_Factor_Core::current_user_being_edited();
 | 
						|
		if ( ! $user_id ) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id );
 | 
						|
 | 
						|
		// @todo Ensure that scripts don't fail because of missing u2fL10n.
 | 
						|
		try {
 | 
						|
			$data              = Two_Factor_FIDO_U2F::$u2f->getRegisterData( $security_keys );
 | 
						|
			list( $req,$sigs ) = $data;
 | 
						|
 | 
						|
			update_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, $req );
 | 
						|
		} catch ( Exception $e ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		wp_enqueue_style(
 | 
						|
			'fido-u2f-admin',
 | 
						|
			plugins_url( 'css/fido-u2f-admin.css', __FILE__ ),
 | 
						|
			null,
 | 
						|
			self::asset_version()
 | 
						|
		);
 | 
						|
 | 
						|
		wp_enqueue_script(
 | 
						|
			'fido-u2f-admin',
 | 
						|
			plugins_url( 'js/fido-u2f-admin.js', __FILE__ ),
 | 
						|
			array( 'jquery', 'fido-u2f-api' ),
 | 
						|
			self::asset_version(),
 | 
						|
			true
 | 
						|
		);
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Pass a U2F challenge and user data to our scripts
 | 
						|
		 */
 | 
						|
 | 
						|
		$translation_array = array(
 | 
						|
			'user_id'  => $user_id,
 | 
						|
			'register' => array(
 | 
						|
				'request' => $req,
 | 
						|
				'sigs'    => $sigs,
 | 
						|
			),
 | 
						|
			'text'     => array(
 | 
						|
				'insert'            => esc_html__( 'Now insert (and tap) your Security Key.', 'two-factor' ),
 | 
						|
				'error'             => esc_html__( 'U2F request failed.', 'two-factor' ),
 | 
						|
				'error_codes'       => array(
 | 
						|
					// Map u2f.ErrorCodes to error messages.
 | 
						|
					0 => esc_html__( 'Request OK.', 'two-factor' ),
 | 
						|
					1 => esc_html__( 'Other U2F error.', 'two-factor' ),
 | 
						|
					2 => esc_html__( 'Bad U2F request.', 'two-factor' ),
 | 
						|
					3 => esc_html__( 'Unsupported U2F configuration.', 'two-factor' ),
 | 
						|
					4 => esc_html__( 'U2F device ineligible.', 'two-factor' ),
 | 
						|
					5 => esc_html__( 'U2F request timeout reached.', 'two-factor' ),
 | 
						|
				),
 | 
						|
				'u2f_not_supported' => esc_html__( 'FIDO U2F appears to be not supported by your web browser. Try using Google Chrome or Firefox.', 'two-factor' ),
 | 
						|
			),
 | 
						|
		);
 | 
						|
 | 
						|
		wp_localize_script(
 | 
						|
			'fido-u2f-admin',
 | 
						|
			'u2fL10n',
 | 
						|
			$translation_array
 | 
						|
		);
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Script for admin UI
 | 
						|
		 */
 | 
						|
 | 
						|
		wp_enqueue_script(
 | 
						|
			'inline-edit-key',
 | 
						|
			plugins_url( 'js/fido-u2f-admin-inline-edit.js', __FILE__ ),
 | 
						|
			array( 'jquery' ),
 | 
						|
			self::asset_version(),
 | 
						|
			true
 | 
						|
		);
 | 
						|
 | 
						|
		wp_localize_script(
 | 
						|
			'inline-edit-key',
 | 
						|
			'inlineEditL10n',
 | 
						|
			array(
 | 
						|
				'error' => esc_html__( 'Error while saving the changes.', 'two-factor' ),
 | 
						|
			)
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Return the current asset version number.
 | 
						|
	 *
 | 
						|
	 * Added as own helper to allow swapping the implementation once we inject
 | 
						|
	 * it as a dependency.
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	protected static function asset_version() {
 | 
						|
		return Two_Factor_FIDO_U2F::asset_version();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Display the security key section in a users profile.
 | 
						|
	 *
 | 
						|
	 * This executes during the `show_user_security_settings` action.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 *
 | 
						|
	 * @param WP_User $user WP_User object of the logged-in user.
 | 
						|
	 */
 | 
						|
	public static function show_user_profile( $user ) {
 | 
						|
		wp_nonce_field( "user_security_keys-{$user->ID}", '_nonce_user_security_keys' );
 | 
						|
		$new_key = false;
 | 
						|
 | 
						|
		$security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user->ID );
 | 
						|
		if ( $security_keys ) {
 | 
						|
			foreach ( $security_keys as &$security_key ) {
 | 
						|
				if ( property_exists( $security_key, 'new' ) ) {
 | 
						|
					$new_key = true;
 | 
						|
					unset( $security_key->new );
 | 
						|
 | 
						|
					// If we've got a new one, update the db record to not save it there any longer.
 | 
						|
					Two_Factor_FIDO_U2F::update_security_key( $user->ID, $security_key );
 | 
						|
				}
 | 
						|
			}
 | 
						|
			unset( $security_key );
 | 
						|
		}
 | 
						|
 | 
						|
		?>
 | 
						|
		<div class="security-keys" id="security-keys-section">
 | 
						|
			<h3><?php esc_html_e( 'Security Keys', 'two-factor' ); ?></h3>
 | 
						|
 | 
						|
			<?php if ( ! is_ssl() ) : ?>
 | 
						|
			<p class="u2f-error-https">
 | 
						|
				<em><?php esc_html_e( 'U2F requires an HTTPS connection. You won\'t be able to add new security keys over HTTP.', 'two-factor' ); ?></em>
 | 
						|
			</p>
 | 
						|
			<?php endif; ?>
 | 
						|
 | 
						|
			<div class="register-security-key">
 | 
						|
				<input type="hidden" name="do_new_security_key" id="do_new_security_key" />
 | 
						|
				<input type="hidden" name="u2f_response" id="u2f_response" />
 | 
						|
				<button type="button" class="button button-secondary" id="register_security_key"><?php echo esc_html( _x( 'Register New Key', 'security key', 'two-factor' ) ); ?></button>
 | 
						|
				<span class="spinner"></span>
 | 
						|
				<span class="security-key-status"></span>
 | 
						|
			</div>
 | 
						|
 | 
						|
			<?php if ( $new_key ) : ?>
 | 
						|
			<div class="notice notice-success is-dismissible">
 | 
						|
				<p class="new-security-key"><?php esc_html_e( 'Your new security key registered.', 'two-factor' ); ?></p>
 | 
						|
			</div>
 | 
						|
			<?php endif; ?>
 | 
						|
 | 
						|
			<p><a href="https://support.google.com/accounts/answer/6103523"><?php esc_html_e( 'You can find FIDO U2F Security Key devices for sale from here.', 'two-factor' ); ?></a></p>
 | 
						|
 | 
						|
			<?php
 | 
						|
				require_once TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin-list-table.php';
 | 
						|
				$u2f_list_table        = new Two_Factor_FIDO_U2F_Admin_List_Table();
 | 
						|
				$u2f_list_table->items = $security_keys;
 | 
						|
				$u2f_list_table->prepare_items();
 | 
						|
				$u2f_list_table->display();
 | 
						|
				$u2f_list_table->inline_edit();
 | 
						|
			?>
 | 
						|
		</div>
 | 
						|
		<?php
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Catch the non-ajax submission from the new form.
 | 
						|
	 *
 | 
						|
	 * This executes during the `personal_options_update` & `edit_user_profile_update` actions.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 *
 | 
						|
	 * @param int $user_id User ID.
 | 
						|
	 * @return false
 | 
						|
	 */
 | 
						|
	public static function catch_submission( $user_id ) {
 | 
						|
		if ( ! empty( $_REQUEST['do_new_security_key'] ) ) {
 | 
						|
			check_admin_referer( "user_security_keys-{$user_id}", '_nonce_user_security_keys' );
 | 
						|
 | 
						|
			try {
 | 
						|
				$response = json_decode( stripslashes( $_POST['u2f_response'] ) );
 | 
						|
				$reg      = Two_Factor_FIDO_U2F::$u2f->doRegister( get_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY, true ), $response );
 | 
						|
				$reg->new = true;
 | 
						|
 | 
						|
				Two_Factor_FIDO_U2F::add_security_key( $user_id, $reg );
 | 
						|
			} catch ( Exception $e ) {
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			delete_user_meta( $user_id, self::REGISTER_DATA_USER_META_KEY );
 | 
						|
 | 
						|
			wp_safe_redirect(
 | 
						|
				add_query_arg(
 | 
						|
					array(
 | 
						|
						'new_app_pass' => 1,
 | 
						|
					),
 | 
						|
					wp_get_referer()
 | 
						|
				) . '#security-keys-section'
 | 
						|
			);
 | 
						|
			exit;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Catch the delete security key request.
 | 
						|
	 *
 | 
						|
	 * This executes during the `load-profile.php` & `load-user-edit.php` actions.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 */
 | 
						|
	public static function catch_delete_security_key() {
 | 
						|
		$user_id = Two_Factor_Core::current_user_being_edited();
 | 
						|
 | 
						|
		if ( ! empty( $user_id ) && ! empty( $_REQUEST['delete_security_key'] ) ) {
 | 
						|
			$slug = $_REQUEST['delete_security_key'];
 | 
						|
 | 
						|
			check_admin_referer( "delete_security_key-{$slug}", '_nonce_delete_security_key' );
 | 
						|
 | 
						|
			Two_Factor_FIDO_U2F::delete_security_key( $user_id, $slug );
 | 
						|
 | 
						|
			wp_safe_redirect( remove_query_arg( 'new_app_pass', wp_get_referer() ) . '#security-keys-section' );
 | 
						|
			exit;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Generate a link to rename a specified security key.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 *
 | 
						|
	 * @param array $item The current item.
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	public static function rename_link( $item ) {
 | 
						|
		return sprintf( '<a href="#" class="editinline">%s</a>', esc_html__( 'Rename', 'two-factor' ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Generate a link to delete a specified security key.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 *
 | 
						|
	 * @param array $item The current item.
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	public static function delete_link( $item ) {
 | 
						|
		$delete_link = add_query_arg( 'delete_security_key', $item->keyHandle ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
 | 
						|
		$delete_link = wp_nonce_url( $delete_link, "delete_security_key-{$item->keyHandle}", '_nonce_delete_security_key' );
 | 
						|
		return sprintf( '<a href="%1$s">%2$s</a>', esc_url( $delete_link ), esc_html__( 'Delete', 'two-factor' ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Ajax handler for quick edit saving for a security key.
 | 
						|
	 *
 | 
						|
	 * @since 0.1-dev
 | 
						|
	 *
 | 
						|
	 * @access public
 | 
						|
	 * @static
 | 
						|
	 */
 | 
						|
	public static function wp_ajax_inline_save() {
 | 
						|
		check_ajax_referer( 'keyinlineeditnonce', '_inline_edit' );
 | 
						|
 | 
						|
		require_once TWO_FACTOR_DIR . 'providers/class-two-factor-fido-u2f-admin-list-table.php';
 | 
						|
		$wp_list_table = new Two_Factor_FIDO_U2F_Admin_List_Table();
 | 
						|
 | 
						|
		if ( ! isset( $_POST['keyHandle'] ) ) {
 | 
						|
			wp_die();
 | 
						|
		}
 | 
						|
 | 
						|
		$user_id       = Two_Factor_Core::current_user_being_edited();
 | 
						|
		$security_keys = Two_Factor_FIDO_U2F::get_security_keys( $user_id );
 | 
						|
		if ( ! $security_keys ) {
 | 
						|
			wp_die();
 | 
						|
		}
 | 
						|
 | 
						|
		foreach ( $security_keys as &$key ) {
 | 
						|
			if ( $key->keyHandle === $_POST['keyHandle'] ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		$key->name = $_POST['name'];
 | 
						|
 | 
						|
		$updated = Two_Factor_FIDO_U2F::update_security_key( $user_id, $key );
 | 
						|
		if ( ! $updated ) {
 | 
						|
			wp_die( esc_html__( 'Item not updated.', 'two-factor' ) );
 | 
						|
		}
 | 
						|
		$wp_list_table->single_row( $key );
 | 
						|
		wp_die();
 | 
						|
	}
 | 
						|
}
 |