478 lines
12 KiB
PHP
478 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Devices JWT-Auth.
|
|
*
|
|
* @package jwt-auth
|
|
*/
|
|
|
|
namespace JWTAuth;
|
|
|
|
/**
|
|
* Display the devices connected with token and let remove them in user profile page.
|
|
* Developed by Rodrigo M. Souza https://github.com/pesseba
|
|
*/
|
|
class Devices {
|
|
|
|
/**
|
|
* Setup action & filter hooks.
|
|
*/
|
|
public function __construct() {
|
|
|
|
add_action( 'show_user_profile', array( $this, 'custom_user_profile_fields' ) );
|
|
add_action( 'edit_user_profile', array( $this, 'custom_user_profile_fields' ) );
|
|
|
|
add_action( 'wp_ajax_remove_device', array( $this, 'remove_device' ) );
|
|
add_shortcode( 'jwt_auth_devices', array( $this, 'shortcode_jwt_auth_devices' ) );
|
|
|
|
add_action( 'profile_update', array( $this, 'profile_update' ), 10, 2 );
|
|
add_action( 'after_password_reset', array( $this, 'after_password_reset' ), 10, 2 );
|
|
add_action( 'user_register', array( $this, 'after_user_creation' ), 10, 1 );
|
|
|
|
add_filter( 'jwt_auth_payload', array( $this, 'jwt_auth_payload' ), 10, 2 );
|
|
add_filter( 'jwt_auth_extra_token_check', array( $this, 'check_device_and_pass' ), 10, 4 );
|
|
}
|
|
|
|
/**
|
|
* Filter payload to add device and pass.
|
|
*
|
|
* @param array $payload The token's payload.
|
|
* @param WP_User $user The user who owns the token.
|
|
*
|
|
* @return array $payload The modified token's payload.
|
|
*/
|
|
public function jwt_auth_payload( $payload, $user ) {
|
|
|
|
$current_device = isset( $_POST['device'] ) ? $this->sanitize_device_name( sanitize_text_field( $_POST['device'] ) ) : ''; // phpcs:ignore
|
|
|
|
// Add device identyfier in user meta if parameter was passed.
|
|
// TODO: considering to use $_SERVER['HTTP_USER_AGENT'] as default value for device in case it is empty.
|
|
if ( ! empty( $current_device ) ) {
|
|
$all_devices = get_user_meta( $user->ID, 'jwt_auth_device', false );
|
|
|
|
if ( empty( $all_devices ) || ! in_array( $current_device, $all_devices, true ) ) {
|
|
$data = array(
|
|
'agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : '', // phpcs:ignore
|
|
'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp', 0 ) ),
|
|
'is_mobile' => wp_is_mobile(),
|
|
);
|
|
|
|
add_user_meta( $user->ID, 'jwt_auth_device', $current_device, false );
|
|
add_user_meta( $user->ID, $this->sanitize_device_key( $current_device ), $data, true );
|
|
}
|
|
}
|
|
|
|
// Add a pass if user doesn't have yet.
|
|
$pass = get_user_meta( $user->ID, 'jwt_auth_pass', true );
|
|
$pass = ( empty( $pass ) ) ? $this->refresh_pass( $user->ID ) : $pass;
|
|
$pass = apply_filters( 'jwt_auth_pass', $pass );
|
|
|
|
$payload['data']['user']['device'] = $current_device;
|
|
$payload['data']['user']['pass'] = $pass;
|
|
|
|
return $payload;
|
|
}
|
|
|
|
/**
|
|
* Filter token validation to check device and pass.
|
|
*
|
|
* @param string $error_msg The failed message.
|
|
* @param WP_User $user The user who owns the token.
|
|
* @param string $token The token.
|
|
* @param array $payload The token's payload.
|
|
*
|
|
* @return string The error message if failed, empty string if it passes.
|
|
*/
|
|
public function check_device_and_pass( $error_msg, $user, $token, $payload ) {
|
|
|
|
// Check if token has device filled.
|
|
if ( ! empty( $payload->data->user->device ) ) {
|
|
|
|
$all_devices = get_user_meta( $user->ID, 'jwt_auth_device', false );
|
|
|
|
if ( ! is_array( $all_devices ) || ! in_array( $payload->data->user->device, $all_devices, true ) ) {
|
|
return 'device unnabled';
|
|
}
|
|
}
|
|
|
|
// Check if user changed the password.
|
|
$pass = get_user_meta( $user->ID, 'jwt_auth_pass', true );
|
|
|
|
if ( $payload->data->user->pass !== $pass ) {
|
|
return 'password changed';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Sanitize the device name.
|
|
*
|
|
* @param string $device The device name.
|
|
* @return string The sanitized device name.
|
|
*/
|
|
private function sanitize_device_name( $device ) {
|
|
|
|
$unwanted_chars = array(
|
|
'Š' => 'S',
|
|
'š' => 's',
|
|
'Ž' => 'Z',
|
|
'ž' => 'z',
|
|
'À' => 'A',
|
|
'Á' => 'A',
|
|
'Â' => 'A',
|
|
'Ã' => 'A',
|
|
'Ä' => 'A',
|
|
'Å' => 'A',
|
|
'Æ' => 'A',
|
|
'Ç' => 'C',
|
|
'È' => 'E',
|
|
'É' => 'E',
|
|
'Ê' => 'E',
|
|
'Ë' => 'E',
|
|
'Ì' => 'I',
|
|
'Í' => 'I',
|
|
'Î' => 'I',
|
|
'Ï' => 'I',
|
|
'Ñ' => 'N',
|
|
'Ò' => 'O',
|
|
'Ó' => 'O',
|
|
'Ô' => 'O',
|
|
'Õ' => 'O',
|
|
'Ö' => 'O',
|
|
'Ø' => 'O',
|
|
'Ù' => 'U',
|
|
'Ú' => 'U',
|
|
'Û' => 'U',
|
|
'Ü' => 'U',
|
|
'Ý' => 'Y',
|
|
'Þ' => 'B',
|
|
'ß' => 'Ss',
|
|
'à' => 'a',
|
|
'á' => 'a',
|
|
'â' => 'a',
|
|
'ã' => 'a',
|
|
'ä' => 'a',
|
|
'å' => 'a',
|
|
'æ' => 'a',
|
|
'ç' => 'c',
|
|
'è' => 'e',
|
|
'é' => 'e',
|
|
'ê' => 'e',
|
|
'ë' => 'e',
|
|
'ì' => 'i',
|
|
'í' => 'i',
|
|
'î' => 'i',
|
|
'ï' => 'i',
|
|
'ð' => 'o',
|
|
'ñ' => 'n',
|
|
'ò' => 'o',
|
|
'ó' => 'o',
|
|
'ô' => 'o',
|
|
'õ' => 'o',
|
|
'ö' => 'o',
|
|
'ø' => 'o',
|
|
'ù' => 'u',
|
|
'ú' => 'u',
|
|
'û' => 'u',
|
|
'ý' => 'y',
|
|
'þ' => 'b',
|
|
'ÿ' => 'y',
|
|
);
|
|
|
|
$device = strtr( $device, $unwanted_chars );
|
|
$device = preg_replace( '/[^a-z0-9 ]/i', '', $device );
|
|
|
|
return $device;
|
|
}
|
|
|
|
/**
|
|
* Sanitize the device key.
|
|
*
|
|
* @param string $key The device key.
|
|
* @return string The sanitized device key.
|
|
*/
|
|
private function sanitize_device_key( $key ) {
|
|
return 'jwt_auth_device_' . str_replace( ' ', '_', $this->sanitize_device_name( $key ) );
|
|
}
|
|
|
|
/**
|
|
* Fires immediately after an existing user is updated.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param int $user_id User ID.
|
|
* @param WP_User $old_user_data Object containing user's data prior to update.
|
|
*/
|
|
public function profile_update( $user_id, $old_user_data ) {
|
|
|
|
$user = get_user_by( 'id', $user_id );
|
|
|
|
if ( $user->user_pass !== $old_user_data->user_pass ) {
|
|
|
|
$this->block_all_tokens( $user_id );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fires after the user's password is reset.
|
|
*
|
|
* @since 4.4.0
|
|
*
|
|
* @param WP_User $user The user.
|
|
* @param string $new_pass New user password.
|
|
*/
|
|
public function after_password_reset( $user, $new_pass ) {
|
|
|
|
$this->block_all_tokens( $user->ID );
|
|
}
|
|
|
|
/**
|
|
* Fires after the user' is created
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param int $user_id The user ID.
|
|
*/
|
|
public function after_user_creation( $user_id ) {
|
|
|
|
$this->refresh_pass( $user_id );
|
|
}
|
|
|
|
/**
|
|
* Block all access tokens.
|
|
*
|
|
* @param int $user_id The user id.
|
|
*/
|
|
private function block_all_tokens( $user_id ) {
|
|
|
|
// Clear devices list with access.
|
|
delete_user_meta( $user_id, 'jwt_auth_device' );
|
|
|
|
global $wpdb;
|
|
|
|
// ! Can we not using a direct database call? Because it is discouraged in wpcs.
|
|
// This is because performance. The key jwt_auth_device_% is has generic key name, so this is necessary. The query uses prepare() to avoid insections anyway
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
"DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key LIKE %s",
|
|
$user_id,
|
|
'jwt_auth_device_%'
|
|
)
|
|
);
|
|
|
|
// Add a hash for the new password.
|
|
$this->refresh_pass( $user_id );
|
|
}
|
|
|
|
/**
|
|
* Refresh the pass value in user meta.
|
|
*
|
|
* @param int $user_id The user id.
|
|
*/
|
|
private function refresh_pass( $user_id ) {
|
|
$pass = (string) md5( uniqid( wp_rand(), true ));
|
|
if(!empty(update_user_meta( $user_id, 'jwt_auth_pass', $pass ) )){
|
|
return $pass;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Remove the device from token access.
|
|
*/
|
|
public function remove_device() {
|
|
|
|
$nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] )) : '';
|
|
$device = isset( $_POST['device'] ) ? sanitize_text_field( $_POST['device'] ) : ''; // phpcs:ignore
|
|
$user_id = isset( $_POST['user_id'] ) && is_numeric( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0; // phpcs:ignore
|
|
|
|
if(!wp_verify_nonce( $nonce, 'jwt_auth_remove_device_'.$user_id)){
|
|
wp_send_json_error();
|
|
wp_die();
|
|
return;
|
|
}
|
|
|
|
if ( delete_user_meta( $user_id, 'jwt_auth_device', $device ) ) {
|
|
delete_user_meta( $user_id, $this->sanitize_device_key( $device ) );
|
|
wp_send_json_success();
|
|
} else {
|
|
wp_send_json_error();
|
|
}
|
|
|
|
wp_die();
|
|
}
|
|
|
|
/**
|
|
* Show custom user profile fields.
|
|
*
|
|
* @param WP_User $profileuser The current WP_User object.
|
|
*/
|
|
public function custom_user_profile_fields( $profile_user ) {
|
|
|
|
?>
|
|
<h2><?php echo wp_kses( __( 'Connected Devices', 'jwt-auth' ) , 'post' ); ?></h2>
|
|
<div id="jwt_auth_devices" style="width:33%">
|
|
<?php echo do_shortcode( '[jwt_auth_devices user_id=' . $profile_user->ID . ']' ); ?>
|
|
</div>
|
|
<?php
|
|
|
|
}
|
|
|
|
/**
|
|
* Shortcode to display the user ranking position.
|
|
*
|
|
* @param array $atts The shortcode attributes.
|
|
*/
|
|
public function shortcode_jwt_auth_devices( $atts ) {
|
|
|
|
if ( ! is_admin() ) {
|
|
return '';
|
|
}
|
|
|
|
$atts = shortcode_atts(
|
|
array(
|
|
'user_id' => get_current_user_id(),
|
|
),
|
|
$atts,
|
|
'jwt_auth_devices'
|
|
);
|
|
|
|
$user_id = absint( $atts['user_id'] );
|
|
|
|
if ( get_current_user_id() !== $user_id ) {
|
|
if ( ! current_user_can( 'administrator' ) ) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
$devices = get_user_meta( $user_id, 'jwt_auth_device', false );
|
|
|
|
if ( count( $devices ) === 0 ) {
|
|
return '<p>' . __( 'You have no devices connected', 'jwt-auth' ) . '</p>';
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
|
|
<style>
|
|
.device_area {margin: 0 -5px;}
|
|
.device_area:after {
|
|
content: "";
|
|
display: table;
|
|
clear: both;
|
|
}
|
|
.device_column {
|
|
float: left;
|
|
width: 200px;
|
|
padding: 10px 10px;
|
|
box-sizing: border-box;
|
|
}
|
|
.device_card {
|
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
|
padding: 16px;
|
|
text-align: center;
|
|
background-color: #f1f1f1;
|
|
}
|
|
.device_title {
|
|
font-size:1vw;
|
|
}
|
|
.device_date {
|
|
font-size:0.7vw;
|
|
}
|
|
.device_agent {
|
|
font-size:0.5vw;
|
|
}
|
|
</style>
|
|
|
|
<script type="text/javascript">
|
|
function jwt_auth_remove_device(user_id, device_name, index) {
|
|
|
|
if (confirm('Are you sure you want to remove this device access?')) {
|
|
|
|
var totalDevices = <?php echo count( $devices ); ?>;
|
|
|
|
for (var i=0; i < totalDevices; i++) {
|
|
var btn = document.getElementById("jwt_auth_remove_button-"+i);
|
|
if(btn!=null) { btn.disabled=true; }
|
|
}
|
|
|
|
var data = {
|
|
'action': 'remove_device',
|
|
'user_id': user_id,
|
|
'device': device_name,
|
|
'nonce': '<?php echo wp_kses( wp_create_nonce('jwt_auth_remove_device_'.$user_id ) , 'post' ); ?>',
|
|
};
|
|
|
|
jQuery.post(ajaxurl, data, function(response) {
|
|
|
|
if (response['success'] == true) {
|
|
|
|
var elem = document.getElementById("jwt_auth_device-"+index);
|
|
elem.parentNode.removeChild(elem);
|
|
|
|
for(var i=0; i < totalDevices; i++){
|
|
var btn = document.getElementById("jwt_auth_remove_button-"+i);
|
|
if(btn!=null) { btn.disabled=false; }
|
|
}
|
|
|
|
} else {
|
|
|
|
alert("<?php echo wp_kses( __( "Ops... device couldn't be removed!", 'jwt-auth' ) , 'post' ); ?>");
|
|
}
|
|
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div id="jwt_auth_devices" class="device_area">
|
|
|
|
<?php
|
|
$line = false;
|
|
|
|
$total_devices = count( $devices );
|
|
|
|
for ( $i = 0; $i < $total_devices; ++$i ) {
|
|
|
|
$device = $devices[ $i ];
|
|
$title = preg_replace( '/(\S{15})(?=\S)/', '$1 ', $device );
|
|
$title = ( strlen( $title ) > 30 ) ? substr( $title, 0, 27 ) . '...' : $title;
|
|
$device_data = (array) get_user_meta( $user_id, $this->sanitize_device_key( $device ), true );
|
|
$icon = ( $device_data['is_mobile'] ) ? 'dashicons-smartphone' : 'dashicons-laptop';
|
|
$date = $device_data['date'];
|
|
$agent = $device_data['agent'];
|
|
$agent = preg_replace( '/(\S{15})(?=\S)/', '$1 ', $agent );
|
|
|
|
?>
|
|
|
|
<div class="device_column" id="jwt_auth_device-<?php echo esc_attr( $i ); ?>">
|
|
<div class="device_card">
|
|
<span class="dashicons <?php echo esc_attr( $icon ); ?>" style="font-size:28px; color:grey;" ></span>
|
|
<p class="device_title"><h3><?php echo esc_html( $title ); ?></h3></p>
|
|
<p class="device_date"><?php echo esc_html( $date ); ?></p>
|
|
<p class="device_agent"><?php echo esc_html( $agent ); ?></p>
|
|
|
|
<?php
|
|
echo '
|
|
<input id="jwt_auth_remove_button-' . esc_attr( $i ) .
|
|
'" class="button wp-generate-pw' .
|
|
'" type="button" value="' . esc_attr( __( 'Remove', 'jwt-auth' )) .
|
|
'" onclick="jwt_auth_remove_device(\'' . esc_attr( $user_id ) . '\',\'' . esc_attr( $device ) . '\',\'' . esc_attr( $i ) . '\' )" />
|
|
';
|
|
?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
?>
|
|
|
|
</div>
|
|
</br>
|
|
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
}
|