Skip to content

Commit

Permalink
Jetpack Protect: blocked login page
Browse files Browse the repository at this point in the history
- adds a better UI on the blocked login page
- allows folks to temporarily unblock themselves with a magic link
  • Loading branch information
roccotripaldi committed Nov 6, 2017
1 parent cb9b285 commit 7f9582c
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 10 deletions.
37 changes: 27 additions & 10 deletions modules/protect.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Jetpack_Protect_Module {
public $last_response_raw;
public $last_response;
private $block_login_with_math;
private $valid_blocked_user_id;

/**
* Singleton implementation
Expand Down Expand Up @@ -344,6 +345,10 @@ function check_preauth( $user = 'Not Used By Protect', $username = 'Not Used By
Jetpack_Protect_Math_Authenticate::math_authenticate();
}

if ( $this->valid_blocked_user_id && $this->valid_blocked_user_id !== $user->ID ) {
return new WP_Error( 'invalid_recovery_token', __( 'The recovery token is not valid for this user.', 'jetpack' ) );
}

return $user;
}

Expand Down Expand Up @@ -541,6 +546,11 @@ function block_with_math() {
* Kill a login attempt
*/
function kill_login() {

if ( $this->blocked_user_is_validated ) {
return;
}

$ip = jetpack_protect_get_ip();
/**
* Fires before every killed login.
Expand All @@ -552,19 +562,25 @@ function kill_login() {
* @param string $ip IP flagged by Protect.
*/
do_action( 'jpp_kill_login', $ip );
$help_url = 'https://jetpack.com/support/security-features/#unblock';

$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations. <a href="%2$s">Find out more...</a>', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ), esc_url( $help_url ) );

if( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations.', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ) );
wp_die(
$die_string,
__( 'Login Blocked by Jetpack', 'jetpack' ),
array ( 'response' => 403 )
);
}

wp_die(
$die_string,
__( 'Login Blocked by Jetpack', 'jetpack' ),
array ( 'response' => 403 )
);
require_once dirname( __FILE__ ) . '/protect/blocked-login-page.php';
$blocked_login_page = new Jetpack_Protect_Blocked_Login_Page( $ip, $this->valid_blocked_user_id );

if ( $blocked_login_page->is_blocked_user_valid() ) {
$this->valid_blocked_user_id = $blocked_login_page->valid_blocked_user_id;
return;
}

$blocked_login_page->render_and_die();
}

/*
Expand Down Expand Up @@ -862,8 +878,9 @@ function get_local_host() {

}

Jetpack_Protect_Module::instance();
$jetpack_protect = Jetpack_Protect_Module::instance();

global $pagenow;
if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) {
Jetpack_Protect_Module::check_login_ability();
$jetpack_protect->check_login_ability();
}
209 changes: 209 additions & 0 deletions modules/protect/blocked-login-page.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<?php


/**
* Class Jetpack_Protect_Blocked_Login_Page
*
* Instanciated on the wp-login page when Jetpack modules are loaded and $pagenow
* is available, or during the login_head hook.
*
* Class will only be instanciated if Protect has detected a hard blocked IP address.
*
*
*/
class Jetpack_Protect_Blocked_Login_Page {

public $can_send_recovery_emails;
public $ip_address;
public $valid_blocked_user_id;
public $page_title;
public $email_address;
public $help_url = 'https://jetpack.com/support/security-features/#unblock';


function __construct( $ip_address, $valid_blocked_user_id ) {
/**
* Filter controls if an email recovery form is shown to blocked IPs.
*
* A recovery form allows folks to re-gain access to the login form
* via an email link if their IP was mistakenly blocked.
*
* @module protect
*
* @since 5.6
*
* @param bool $can_send_recovery_emails Defaults to true.
*/
$this->can_send_recovery_emails = apply_filters( 'jetpack_protect_can_send_recovery_emails', true );
$this->ip_address = $ip_address;
$this->valid_blocked_user_id = $valid_blocked_user_id;
}

public function is_blocked_user_valid() {
if ( ! $this->can_send_recovery_emails ) {
return false;
}

if ( $this->valid_blocked_user_id ) {
return true;
}

if ( ! isset( $_GET['validate_jetpack_protect_recovery'], $_GET['user_id'] ) ) {
return false;
}

if ( ! $this->is_valid_protect_recovery_key( $_GET['validate_jetpack_protect_recovery'], $_GET['user_id'] ) ) {
return false;
}

$this->valid_blocked_user_id = $_GET['user_id'];
return true;
}

public function is_valid_protect_recovery_key( $key, $user_id ) {

$path = sprintf( '/sites/%d/protect/recovery/confirm', Jetpack::get_option( 'id' ) );
$response = Jetpack_Client::wpcom_json_api_request_as_blog(
$path,
'1.1',
array(
'method' => 'post'
),
array(
'token' => $key,
'user_id' => $user_id,
'ip' => $this->ip_address,
)
);

$result = json_decode( wp_remote_retrieve_body( $response ) );

if ( is_wp_error( $result ) || empty( $result ) || isset( $result->error ) ) {
return false;
}
return true;
}

public function render_and_die() {
$this->page_title = __( 'Login Blocked by Jetpack', 'jetpack' );

if ( ! $this->can_send_recovery_emails ) {
$this->render_blocked_login_message();
return;
}

if ( isset( $_GET['validate_jetpack_protect_recovery'] ) && $_GET['user_id'] ) {
$this->protect_die( __( 'Could not validate recovery token.', 'jetpack' ) );
return;
}

if (
isset( $_GET['jetpack-protect-recovery'] ) &&
isset( $_POST['_wpnonce'] ) &&
wp_verify_nonce( $_POST['_wpnonce'], 'bypass-protect' )
) {
$this->process_recovery_email();
return;
}

$this->render_recovery_form();
}

public function render_blocked_login_message() {
$this->protect_die( $this->get_html_blocked_login_message() );
}

function process_recovery_email() {
$sent = $this->send_recovery_email();

if ( is_wp_error( $sent ) ) {
$this->protect_die( $sent, true );
} else {
$this->render_recovery_success();
}
}

function send_recovery_email() {
$email = isset( $_POST['email'] ) ? $_POST['email'] : '';
if ( sanitize_email( $email ) !== $email || ! is_email( $email ) ) {
return new WP_Error( 'invalid_email', __( "Oops, looks like that's not the right email address. Please try again!", 'jetpack' ) );
}
$user = get_user_by('email', trim( $email ) );

if ( ! $user ) {
return new WP_Error( 'invalid_user', __( 'Oops, could not find a user with that email address.', 'jetpack' ) );
}
$this->email_address = $email;
$path = sprintf( '/sites/%d/protect/recovery/request', Jetpack::get_option( 'id' ) );


$response = Jetpack_Client::wpcom_json_api_request_as_blog(
$path,
'1.1',
array(
'method' => 'post'
),
array(
'user_id' => $user->ID,
'ip' => $this->ip_address
)
);

$code = wp_remote_retrieve_response_code( $response );
$result = json_decode( wp_remote_retrieve_body( $response ) );

if ( 429 === $code ) {
return new WP_Error( 'email_already_sent', __( 'An email was already sent to this address.', 'jetpack' ) );
} else if ( is_wp_error( $result ) || empty( $result ) || isset( $result->error ) ) {
return new WP_Error( 'email_send_error', __( 'There was an error sending your email.', 'jetpack' ) );
}
return true;
}

function protect_die( $content, $back_link = false ) {
$image = sprintf(
'<img src="%s" width="180" style="display: block; margin: 0 auto;" />',
plugins_url( 'modules/protect/jetpack-security.png', JETPACK__PLUGIN_FILE )
);

if ( is_wp_error( $content ) ) {
$content = $content->get_error_message();
}
// hack to get around default wp_die_handler. https://core.trac.wordpress.org/browser/tags/4.8.1/src/wp-includes/functions.php#L2698
$content = $image . '</p> ' . $content . '<p>';

wp_die( $content, $this->page_title, array( 'back_link' => $back_link ) );
}

function render_recovery_form() {
$content = $this->get_html_blocked_login_message() . $this->get_html_recovery_form();
$this->protect_die( $content );
}

function render_recovery_success() {
$this->protect_die( sprintf( __( 'An email with recovery instructions was sent to %s.', 'jetpack' ), $this->email_address ) );
}

function get_html_blocked_login_message() {
return sprintf(
__( '<p>Your IP (%1$s) has been flagged for potential security violations.</p>', 'jetpack' ),
str_replace( 'http://', '', esc_url( 'http://' . $this->ip_address )
) );
}

function get_html_recovery_form() {
ob_start(); ?>
<p><?php _e( 'Email yourself a special link to regain access the login form.', 'jetpack' ); ?></p>
<form method="post" action="?jetpack-protect-recovery=true">
<?php echo wp_nonce_field( 'bypass-protect' ); ?>
<label for="email">Email Address:</label>
<input type="email" name="email" />
<input type="submit" value="<?php echo esc_attr( __( 'Send', 'jetpack' ) ); ?>" />
</form>

<?php
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
}
Binary file added modules/protect/jetpack-security.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7f9582c

Please sign in to comment.