Verified Commit 5cc2efa9 authored by noplanman's avatar noplanman
Browse files

Initial rewrite.

parent d054781f
before_commands:
- "composer install --prefer-dist"
checks:
php:
code_rating: true
duplication: false
tools:
php_analyzer: true
php_mess_detector: true
php_code_sniffer:
config:
standard: "WordPress"
filter:
excluded_paths:
- "vendor/*"
# wp-otp
OTP for WordPress
<?php
/**
* The admin-specific functionality of the plugin
*
* @package Wp_Otp
* @subpackage Admin
* @since 0.1.0
*/
namespace Wp_Otp;
use OTPHP\TOTP;
use WP_User;
defined( 'WPINC' ) || exit;
/**
* The admin-specific functionality of the plugin.
*
* @since 0.1.0
*/
class Wp_Otp_Admin {
/**
* Check and save the OTP data when saving the user profile.
*
* @since 0.1.0
*
* @param int $user_id
*
* @return void
*/
public function user_profile_updated( $user_id ) {
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return;
}
$user = get_userdata( $user_id );
$user_meta_data = Wp_Otp_User_Meta::get_instance();
$otp = new TOTP(
$user->user_login,
$user_meta_data->get_user_meta( 'secret' )
);
$otp->setIssuer( get_option( 'blogname' ) );
$secret = $otp->getSecret();
$user_meta_data->set_user_meta( 'secret', $secret );
$otp_code = trim( $_POST['wp_otp_code'] );
if ( $otp_code && ! $user_meta_data->get_user_meta( 'enabled', false ) ) {
$otp_window = (int) apply_filters( 'wp_otp_code_expiration_window', 2 );
$verification = $otp->verify( $otp_code, null, $otp_window );
if ( $verification ) {
$otp_recovery = bin2hex( random_bytes( 8 ) );
$user_meta_data->set_user_metas( [
'enabled' => true,
'recovery' => $otp_recovery,
'notice' => [
'type' => 'success',
'messages' => [
'<strong>' . __( 'WP-OTP configured successfully!', 'wp-otp' ) . '</strong>',
__( 'If you change your phone or do not have access to the OTP Authenticator app you can use the following key as a One Time Password on your login screen and then reconfigure WP OTP. Never share this key with anyone!',
'wp-otp' ),
$otp_recovery,
],
],
] );
} else {
Wp_Otp_User_Meta::delete();
$user_meta_data->set_user_metas( [
'secret' => $secret,
'notice' => [
'type' => 'error',
'messages' => [
'<strong>' . __( 'WP-OTP configuration failed.', 'wp-otp' ) . '</strong>',
__( 'The One Time Password entered was invalid! Please try again.', 'wp-otp' ),
],
],
] );
}
$user_meta_data->save();
}
}
/**
* Check if the OTP is being deleted and reconfigured.
*
* @since 0.1.0
*/
public function admin_init() {
if ( array_key_exists( 'wp-otp-delete', $_GET ) ) {
Wp_Otp_User_Meta::delete();
wp_redirect( get_edit_profile_url() );
exit;
}
}
/**
* Render the WP-OTP section on the user's profile edit screen.
*
* @since 0.1.0
*
* @param WP_User $user
*/
public function user_profile_render( $user ) {
$user_meta_data = Wp_Otp_User_Meta::get_instance();
// Get the secret.
$secret = $user_meta_data->get_user_meta( 'secret' );
$otp = new TOTP( $user->user_login, $secret );
// Check if the secret was loaded from the meta or not.
if ( null === $secret ) {
$secret = $otp->getSecret();
$user_meta_data->set_user_meta( 'secret', $secret, true );
}
/**
* Filter for the OTP QR code provisioning URI.
*
* Set a custom QR code provisioning URI which has a data placeholder of {PROVISIONING_URI}.
*
* @since 0.1.0
*
* @param string $otp_window
*/
$otp_qr_code_uri = $otp->getQrCodeUri( apply_filters(
'wp_otp_qr_code_provisioning_uri',
'https://api.qrserver.com/v1/create-qr-code/?data={PROVISIONING_URI}&qzone=2&size=300x300'
) );
$otp_enabled = $user_meta_data->get_user_meta( 'enabled', false );
$otp_apps = [
[
'name' => 'FreeOTP',
'uri' => 'https://fedorahosted.org/freeotp/',
'uri_app_store' => 'https://itunes.apple.com/us/app/freeotp-authenticator/id872559395',
'uri_play_store' => 'https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp',
'uri_f_droid' => 'https://f-droid.org/repository/browse/?fdid=org.fedorahosted.freeotp',
'uri_logo' => plugins_url( 'images/freeotp.png', __FILE__ ),
],
[
'name' => 'Google Authenticator',
'uri' => 'https://github.com/google/google-authenticator/',
'uri_app_store' => 'https://itunes.apple.com/us/app/google-authenticator/id388497605',
'uri_play_store' => 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2',
'uri_f_droid' => 'https://f-droid.org/repository/browse/?fdid=com.google.android.apps.authenticator2',
'uri_logo' => plugins_url( 'images/google-authenticator.png', __FILE__ ),
],
];
$app_providers = [
'f_droid' => [ 'name' => 'F-Droid', 'uri_logo' => plugins_url( 'images/f-droid.png', __FILE__ ), ],
'play_store' => [ 'name' => 'Play Store', 'uri_logo' => plugins_url( 'images/play-store.png', __FILE__ ), ],
'app_store' => [ 'name' => 'App Store', 'uri_logo' => plugins_url( 'images/app-store.png', __FILE__ ), ],
];
include __DIR__ . '/partials/wp-otp-profile-display.php';
}
/**
* Show the user a notification.
*
* @since 0.1.0
*
* @param array $messages List of messages to be displayed.
* @param string $type Type of notification to show (notice (default), success, error).
*
* @return void
*/
public function show_user_notification( array $messages, $type = 'notice' ) {
if ( ! empty( $messages ) ) {
return;
}
$classes = [
'notice' => 'update-nag',
'success' => 'updated',
'error' => 'error',
];
$class = $classes[ array_key_exists( $type, $classes ) ? $type : 'notice' ];
?>
<div id="message" class="<?php echo esc_attr( $class ); ?>">
<p><?php echo implode( '<br>', $messages ); ?></p>
</div>
<?php
}
/**
* Display any saved admin notices.
*
* These notices are saved to the user meta and get cleared after showing.
*
* @since 0.1.0
*/
public function admin_notices() {
$user_meta_data = Wp_Otp_User_Meta::get_instance();
if ( ! $user_meta_data->get_user_meta( 'enabled', false ) ) {
$this->show_user_notification( [
__( '<strong>Note:</strong> You have not yet configured WP-OTP.', 'wp-otp' ),
sprintf(
'<a href="%1$s#wp_otp">%2$s</a>',
get_edit_profile_url(),
_x( 'Configure now', 'Link text to go to WP-OTP section in user profile', 'wp-otp' )
),
] );
} elseif ( null === $user_meta_data->get_user_meta( 'recovery' ) ) {
$this->show_user_notification( [
__( '<strong>Important:</strong> You have used your WP-OTP recovery hash. You must generate a new one.',
'wp-otp' ),
sprintf(
'<a href="%1$s#wp_otp">%2$s</a>',
get_edit_profile_url(),
_x( 'Configure now', 'Link text to go to WP-OTP section in user profile', 'wp-otp' )
),
], 'error' );
}
if ( $notice = $user_meta_data->get_user_meta( 'notice' ) ) {
$this->show_user_notification(
(array) $notice['messages'],
$notice['type']
);
// Remove any notices from the user meta.
$user_meta_data->set_user_meta( 'notice', null, true );
}
}
}
<?php // Silence is golden
\ No newline at end of file
<?php // Silence is golden
\ No newline at end of file
<?php
/**
* Render the WP-OTP section in the user profile in WP Admin
*
* @since 0.1.0
*/
?>
<a name="wp_otp"></a>
<h2>Set up WP-OTP</h2>
<table class="form-table">
<tr>
<th>
<label for="wp_otp_qr_code_img"><?php _e( 'QR Code', 'wp-otp' ); ?></label><br>
<span class="description">Download any OTP Authenticator app on your smart phone and
scan this QR Code to setup WP-OTP.</span>
</th>
<td width="40%">
<img src="<?php echo $otp_qr_code_uri; ?>" id="wp_otp_qr_code_img"/><br>
<?php printf( __( 'OTP Secret: %s', 'wp-otp' ), implode( ' ', str_split( $secret, 4 ) ) ); ?>
</td>
<td>
<?php foreach ( $otp_apps as $otp_app ): ?>
<?php $app_name = esc_attr( $otp_app['name'] ); ?>
<a href="<?php echo $otp_app['uri']; ?>"><strong><?php echo $otp_app['name']; ?></strong><br>
<img src="<?php echo $otp_app['uri_logo']; ?>"
alt="<?php echo $app_name; ?>"
title="<?php echo $app_name; ?>"
/></a>&nbsp;
<?php foreach ( $app_providers as $app_provider_key => $app_provider ): ?>
<?php
$get_it_on_text = sprintf(
esc_attr__( 'Get it on %s', 'wp-otp' ),
$app_provider['name']
);
?>
<a href="<?php echo $otp_app[ 'uri_' . $app_provider_key ]; ?>" target="_blank">
<img src="<?php echo $app_provider['uri_logo']; ?>"
alt="<?php echo $get_it_on_text; ?>"
title="<?php echo $get_it_on_text; ?>"
/></a>&nbsp;
<?php endforeach; ?>
<br><br>
<?php endforeach; ?>
<?php _e( 'Blackberry users can search and install any OTP app on their phone.', 'wp-otp' ); ?>
</td>
</tr>
<tr>
<?php if ( $otp_enabled ): ?>
<th>
<?php _e( 'WP-OTP Configured', 'wp-otp' ); ?>
</th>
<td colspan="2">
<?php
printf(
'%1$s <a href="%2$s" onclick = "return confirm(\'%3$s\')">%4$s</a>',
__( 'WP-OTP is already configured.', 'wp-otp' ),
get_edit_profile_url() . '?wp-otp-delete',
'Are you sure you want to reconfigure WP-OTP?',
__( 'Reconfigure?', 'wp-otp' )
);
?>
</td>
<?php else: ?>
<th>
<label for="wp_otp_code"><?php _e( 'One Time Password', 'wp-otp' ); ?></label>
</th>
<td colspan="2">
<input type="text" size="25"
value="<?php echo isset( $_POST['wp_otp_code'] ) ? $_POST['wp_otp_code'] : ''; ?>"
name="wp_otp_code" id="wp_otp_code"/><br/>
<?php _e( 'Enter the One Time Password from Google Authenticator app on your smartphone <br>
WP-OTP will not work unless you Enter the OTP here and click on <b>Update Profile</b> button below.',
'wp-otp' ); ?>
</td>
<?php endif; ?>
</tr>
</table>
{
"name": "noplanman/wp-otp",
"type": "wordpress-plugin",
"description": "OTP for WordPress",
"keywords": ["otp", "totp", "hotp", "plugin", "wordpress"],
"license": "GPL-3.0",
"homepage": "https://github.com/noplanman/wp-otp",
"support": {
"issues": "https://github.com/noplanman/wp-otp/issues",
"source": "https://github.com/noplanman/wp-otp"
},
"authors": [
{
"name": "Armando Lüscher",
"email": "armando@noplanman.ch",
"homepage": "https://noplanman.ch",
"role": "Developer"
}
],
"require": {
"php": "^5.5|^7.0",
"spomky-labs/otphp": "^8.2"
}
}
<?php
/**
* Define the internationalisation functionality
*
* Loads and defines the internationalisation files for this plugin
* so that it is ready for translation.
*
* @package Wp_Otp
* @subpackage Internationalisation
* @since 0.1.0
*/
namespace Wp_Otp;
defined( 'WPINC' ) || exit;
/**
* Define the internationalisation functionality.
*
* Loads and defines the internationalisation files for this plugin so that it is ready for translation.
*
* @since 0.1.0
*/
class Wp_Otp_i18n {
/**
* Load the plugin text domain for translation.
*
* @since 0.1.0
*/
public function load_plugin_textdomain() {
load_plugin_textdomain(
WP_OTP_SLUG,
false,
dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
);
}
}
<?php
/**
* Register all actions and filters for the plugin
*
* @package Wp_Otp
* @subpackage Loader
* @since 0.1.0
*/
namespace Wp_Otp;
defined( 'WPINC' ) || exit;
/**
* Register all actions and filters for the plugin.
*
* Maintain a list of all hooks that are registered throughout the plugin and register them with the WordPress API.
*
* @since 0.1.0
*/
class Wp_Otp_Loader {
/**
* The actions registered with WordPress to fire when the plugin loads.
*
* @since 0.1.0
* @access private
* @var array $actions
*/
private $actions;
/**
* The filters registered with WordPress to fire when the plugin loads.
*
* @since 0.1.0
* @access private
* @var array $filters
*/
private $filters;
/**
* Initialize the collections used to maintain the actions and filters.
*
* @since 0.1.0
*/
public function __construct() {
$this->actions = [];
$this->filters = [];
}
/**
* Add a new action to the collection to be registered with WordPress.
*
* @since 0.1.0
*
* @param string $hook The name of the WordPress action that is being registered.
* @param object $component A reference to the instance of the object on which the action is defined.
* @param string $callback The name of the function definition on the $component. Default is the $hook name.
* @param int $priority The priority at which the function should be fired. Default is 10.
* @param int $accepted_args The number of arguments that should be passed to the $callback. Default is 1.
*/
public function add_action( $hook, $component, $callback = null, $priority = 10, $accepted_args = 1 ) {
$this->actions = $this->add( $this->actions, $hook, $component, $callback ?: $hook, $priority, $accepted_args );
}
/**
* Add a new filter to the collection to be registered with WordPress.
*
* @since 0.1.0
*
* @param string $hook The name of the WordPress filter that is being registered.
* @param object $component A reference to the instance of the object on which the filter is defined.
* @param string $callback The name of the function definition on the $component. Default is the $hook name.
* @param int $priority The priority at which the function should be fired. Default is 10.
* @param int $accepted_args The number of arguments that should be passed to the $callback. Default is 1.
*/
public function add_filter( $hook, $component, $callback = null, $priority = 10, $accepted_args = 1 ) {
$this->filters = $this->add( $this->filters, $hook, $component, $callback ?: $hook, $priority, $accepted_args );
}
/**
* A utility function that is used to register the actions and hooks into a single
* collection.
*
* @since 0.1.0
* @access private
*
* @param array $hooks The collection of hooks that is being registered (that is, actions or filters).
* @param string $hook The name of the WordPress filter that is being registered.
* @param object $component A reference to the instance of the object on which the filter is defined.
* @param string $callback The name of the function definition on the $component.
* @param int $priority The priority at which the function should be fired.
* @param int $accepted_args The number of arguments that should be passed to the $callback.
*
* @return array The collection of actions and filters registered with WordPress.
*/
private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) {
$hooks[] = [
'hook' => $hook,
'component' => $component,
'callback' => $callback,
'priority' => $priority,
'accepted_args' => $accepted_args,
];
return $hooks;
}
/**
* Register the filters and actions with WordPress.
*
* @since 0.1.0
*/
public function run() {
foreach ( $this->filters as $hook ) {
add_filter(
$hook['hook'],
[ $hook['component'], $hook['callback'] ],
$hook['priority'],
$hook['accepted_args']
);
}
foreach ( $this->actions as $hook ) {
add_action(
$hook['hook'],
[ $hook['component'], $hook['callback'] ],
$hook['priority'],
$hook['accepted_args']
);
}
}
}
<?php
/**
* Handle all activation, deactivation and uninstallation tasks
*
* @package Wp_Otp
* @subpackage Setup
* @since 0.1.0
*/
namespace Wp_Otp;
defined( 'WPINC' ) || exit;
/**
* Handle all activation, deactivation and uninstallation tasks.
*
* @since 0.1.0
*/
class Wp_Otp_Setup {
/**
* Activation on a single or multisite network.
*
* @since 0.1.0
*
* @param bool $network_wide TRUE if multisite/network and superadmin uses the "Network Activate" action.
* FALSE is no multisite install or plugin gets activated on a single blog.
*/
public static function activate( $network_wide ) {
if ( $network_wide && is_multisite() ) {
foreach ( get_sites() as $site ) {
switch_to_blog( $site->blog_id );
self::do_activation();
}
restore_current_blog();
} else {
self::do_activation();
}
}
/**
* Deactivation on a single or multisite/network.
*
* @since 0.1.0
*
* @param bool $network_wide TRUE if multisite/network and superadmin uses the "Network Deactivate" action.
* FALSE is no multisite install or plugin gets deactivated on a single blog.
*/
public static function deactivate( $network_wide ) {
if ( $network_wide && is_multisite() ) {
foreach ( get_sites() as $site ) {
switch_to_blog( $site->blog_id );
self::do_deactivation();
}
restore_current_blog();
} else {
self::do_deactivation();
}