bottega/simple-membership/classes/class.swpm-auth.php

402 lines
15 KiB
PHP

<?php
class SwpmAuth {
public $protected;
public $permitted;
private $isLoggedIn;
private $lastStatusMsg;
private static $_this;
public $userData;
private function __construct() {
//check if we need to display custom message on the login form
$custom_msg = filter_input( INPUT_COOKIE, 'swpm-login-form-custom-msg', FILTER_SANITIZE_STRING );
if ( ! empty( $custom_msg ) ) {
$this->lastStatusMsg = $custom_msg;
//let's 'unset' the cookie
setcookie( 'swpm-login-form-custom-msg', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN );
}
$this->isLoggedIn = false;
$this->userData = null;
$this->protected = SwpmProtection::get_instance();
}
private function init() {
$valid = $this->validate();
//SwpmLog::log_auth_debug("init:". ($valid? "valid": "invalid"), true);
if ( ! $valid ) {
$this->authenticate();
}
}
public static function get_instance() {
if ( empty( self::$_this ) ) {
self::$_this = new SwpmAuth();
self::$_this->init();
}
return self::$_this;
}
private function authenticate( $user = null, $pass = null ) {
global $wpdb;
$swpm_password = empty( $pass ) ? filter_input( INPUT_POST, 'swpm_password' ) : $pass;
$swpm_user_name = empty( $user ) ? apply_filters( 'swpm_user_name', filter_input( INPUT_POST, 'swpm_user_name' ) ) : $user;
if ( ! empty( $swpm_user_name ) && ! empty( $swpm_password ) ) {
//SWPM member login request.
//Trigger action hook that can be used to check stuff before the login request is processed by the plugin.
$args = array(
'username' => $swpm_user_name,
'password' => $swpm_password,
);
do_action( 'swpm_before_login_request_is_processed', $args );
//First, lets make sure this user is not already logged into the site as an "Admin" user. We don't want to override that admin login session.
if ( current_user_can( 'administrator' ) ) {
//This user is logged in as ADMIN then trying to do another login as a member. Stop the login request processing (we don't want to override your admin login session).
$wp_profile_page = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/wp-admin/profile.php';
$error_msg = '';
$error_msg .= '<p>' . SwpmUtils::_( 'Warning! Simple Membership plugin cannot process this login request to prevent you from getting logged out of WP Admin accidentally.' ) . '</p>';
$error_msg .= '<p><a href="' . $wp_profile_page . '" target="_blank">' . SwpmUtils::_( 'Click here' ) . '</a>' . SwpmUtils::_( ' to see the profile you are currently logged into in this browser.' ) . '</p>';
$error_msg .= '<p>' . SwpmUtils::_( 'You are logged into the site as an ADMIN user in this browser. First, logout from WP Admin then you will be able to log in as a normal member.' ) . '</p>';
$error_msg .= '<p>' . SwpmUtils::_( 'Alternatively, you can use a different browser (where you are not logged-in as ADMIN) to test the membership login.' ) . '</p>';
$error_msg .= '<p>' . SwpmUtils::_( 'Your normal visitors or members will never see this message. This message is ONLY for ADMIN user.' ) . '</p>';
wp_die( $error_msg );
}
//If captcha is present and validation failed, it returns an error string. If validation succeeds, it returns an empty string.
$captcha_validation_output = apply_filters( 'swpm_validate_login_form_submission', '' );
if ( ! empty( $captcha_validation_output ) ) {
$this->lastStatusMsg = SwpmUtils::_( 'Captcha validation failed on login form.' );
return;
}
if ( is_email( $swpm_user_name ) ) {//User is trying to log-in using an email address
$email = sanitize_email( $swpm_user_name );
$query = $wpdb->prepare( 'SELECT user_name FROM ' . $wpdb->prefix . 'swpm_members_tbl WHERE email = %s', $email );
$username = $wpdb->get_var( $query );
if ( $username ) {//Found a user record
$swpm_user_name = $username; //Grab the usrename value so it can be used in the authentication process.
SwpmLog::log_auth_debug( 'Authentication request using email address: ' . $email . ', Found a user record with username: ' . $swpm_user_name, true );
}
}
//Lets process the request. Check username and password
$user = sanitize_user( $swpm_user_name );
$pass = trim( $swpm_password );
SwpmLog::log_auth_debug( 'Authentication request - Username: ' . $swpm_user_name, true );
$query = 'SELECT * FROM ' . $wpdb->prefix . 'swpm_members_tbl WHERE user_name = %s';
$userData = $wpdb->get_row( $wpdb->prepare( $query, $user ) );
$this->userData = $userData;
if ( ! $userData ) {
$this->isLoggedIn = false;
$this->userData = null;
$this->lastStatusMsg = SwpmUtils::_( 'User Not Found.' );
return false;
}
$check = $this->check_password( $pass, $userData->password );
if ( ! $check ) {
$this->isLoggedIn = false;
$this->userData = null;
$this->lastStatusMsg = SwpmUtils::_( 'Password Empty or Invalid.' );
return false;
}
if ( $this->check_constraints() ) {
$rememberme = filter_input( INPUT_POST, 'rememberme' );
$remember = empty( $rememberme ) ? false : true;
$this->set_cookie( $remember );
$this->isLoggedIn = true;
$this->lastStatusMsg = 'Logged In.';
SwpmLog::log_auth_debug( 'Authentication successful for username: ' . $user . '. Executing swpm_login action hook.', true );
do_action( 'swpm_login', $user, $pass, $remember );
return true;
}
}
return false;
}
private function check_constraints() {
if ( empty( $this->userData ) ) {
return false;
}
global $wpdb;
$enable_expired_login = SwpmSettings::get_instance()->get_value( 'enable-expired-account-login', '' );
//Update the last accessed date and IP address for this login attempt. $wpdb->update(table, data, where, format, where format)
$last_accessed_date = current_time( 'mysql' );
$last_accessed_ip = SwpmUtils::get_user_ip_address();
$wpdb->update(
$wpdb->prefix . 'swpm_members_tbl',
array(
'last_accessed' => $last_accessed_date,
'last_accessed_from_ip' => $last_accessed_ip,
),
array( 'member_id' => $this->userData->member_id ),
array( '%s', '%s' ),
array( '%d' )
);
//Check the member's account status.
$can_login = true;
if ( $this->userData->account_state == 'inactive' && empty( $enable_expired_login ) ) {
$this->lastStatusMsg = SwpmUtils::_( 'Account is inactive.' );
$can_login = false;
} elseif ( ( $this->userData->account_state == 'expired' ) && empty( $enable_expired_login ) ) {
$this->lastStatusMsg = SwpmUtils::_( 'Account has expired.' );
$can_login = false;
} elseif ( $this->userData->account_state == 'pending' ) {
$this->lastStatusMsg = SwpmUtils::_( 'Account is pending.' );
$can_login = false;
} elseif ( $this->userData->account_state == 'activation_required' ) {
$resend_email_url = add_query_arg(
array(
'swpm_resend_activation_email' => '1',
'swpm_member_id' => $this->userData->member_id,
),
get_home_url()
);
$msg = sprintf( SwpmUtils::_( 'You need to activate your account. If you didn\'t receive an email then %s to resend the activation email.' ), '<a href="' . $resend_email_url . '">' . SwpmUtils::_( 'click here' ) . '</a>' );
$this->lastStatusMsg = $msg;
$can_login = false;
}
if ( ! $can_login ) {
$this->isLoggedIn = false;
$this->userData = null;
return false;
}
if ( SwpmUtils::is_subscription_expired( $this->userData ) ) {
if ( $this->userData->account_state == 'active' ) {
$wpdb->update( $wpdb->prefix . 'swpm_members_tbl', array( 'account_state' => 'expired' ), array( 'member_id' => $this->userData->member_id ), array( '%s' ), array( '%d' ) );
}
if ( empty( $enable_expired_login ) ) {
$this->lastStatusMsg = SwpmUtils::_( 'Account has expired.' );
$this->isLoggedIn = false;
$this->userData = null;
return false;
}
}
$this->permitted = SwpmPermission::get_instance( $this->userData->membership_level );
$this->lastStatusMsg = SwpmUtils::_( 'You are logged in as:' ) . $this->userData->user_name;
$this->isLoggedIn = true;
return true;
}
private function check_password( $plain_password, $hashed_pw ) {
global $wp_hasher;
if ( empty( $plain_password ) ) {
return false;
}
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . 'wp-includes/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
return $wp_hasher->CheckPassword( $plain_password, $hashed_pw );
}
public function match_password( $password ) {
if ( ! $this->is_logged_in() ) {
return false;
}
return $this->check_password( $password, $this->get( 'password' ) );
}
public function login_to_swpm_using_wp_user( $user ) {
if ( $this->isLoggedIn ) {
return false;
}
$email = $user->user_email;
$member = SwpmMemberUtils::get_user_by_email( $email );
if ( empty( $member ) ) {
//There is no swpm profile with this email.
return false;
}
$this->userData = $member;
$this->isLoggedIn = true;
$this->set_cookie();
SwpmLog::log_auth_debug( 'Member has been logged in using WP User object.', true );
$this->check_constraints();
return true;
}
public function login( $user, $pass, $remember = '', $secure = '' ) {
SwpmLog::log_auth_debug( 'SwpmAuth::login()', true );
if ( $this->isLoggedIn ) {
return;
}
if ( $this->authenticate( $user, $pass ) && $this->validate() ) {
$this->set_cookie( $remember, $secure );
} else {
$this->isLoggedIn = false;
$this->userData = null;
}
return $this->lastStatusMsg;
}
public function logout() {
if ( ! $this->isLoggedIn ) {
return;
}
setcookie( SIMPLE_WP_MEMBERSHIP_AUTH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
setcookie( SIMPLE_WP_MEMBERSHIP_SEC_AUTH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
$this->userData = null;
$this->isLoggedIn = false;
$this->lastStatusMsg = SwpmUtils::_( 'Logged Out Successfully.' );
do_action( 'swpm_logout' );
}
private function set_cookie( $remember = '', $secure = '' ) {
if ( $remember ) {
$expiration = time() + 1209600; //14 days
$expire = $expiration + 43200; //12 hours grace period
} else {
$expiration = time() + 259200; //3 days.
$expire = $expiration; //The minimum cookie expiration should be at least a few days.
}
$expire = apply_filters( 'swpm_auth_cookie_expiry_value', $expire );
setcookie( 'swpm_in_use', 'swpm_in_use', $expire, COOKIEPATH, COOKIE_DOMAIN );
$expiration_timestamp = SwpmUtils::get_expiration_timestamp( $this->userData );
$enable_expired_login = SwpmSettings::get_instance()->get_value( 'enable-expired-account-login', '' );
// make sure cookie doesn't live beyond account expiration date.
// but if expired account login is enabled then ignore if account is expired
$expiration = empty( $enable_expired_login ) ? min( $expiration, $expiration_timestamp ) : $expiration;
$pass_frag = substr( $this->userData->password, 8, 4 );
$scheme = 'auth';
if ( ! $secure ) {
$secure = is_ssl();
}
$key = self::b_hash( $this->userData->user_name . $pass_frag . '|' . $expiration, $scheme );
$hash = hash_hmac( 'md5', $this->userData->user_name . '|' . $expiration, $key );
$auth_cookie = $this->userData->user_name . '|' . $expiration . '|' . $hash;
$auth_cookie_name = $secure ? SIMPLE_WP_MEMBERSHIP_SEC_AUTH : SIMPLE_WP_MEMBERSHIP_AUTH;
setcookie( $auth_cookie_name, $auth_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure, true );
}
private function validate() {
$auth_cookie_name = is_ssl() ? SIMPLE_WP_MEMBERSHIP_SEC_AUTH : SIMPLE_WP_MEMBERSHIP_AUTH;
if ( ! isset( $_COOKIE[ $auth_cookie_name ] ) || empty( $_COOKIE[ $auth_cookie_name ] ) ) {
return false;
}
$cookie_elements = explode( '|', $_COOKIE[ $auth_cookie_name ] );
if ( count( $cookie_elements ) != 3 ) {
return false;
}
//SwpmLog::log_auth_debug("validate() - " . $_COOKIE[$auth_cookie_name], true);
list($username, $expiration, $hmac) = $cookie_elements;
$expired = $expiration;
// Allow a grace period for POST and AJAX requests
if ( defined( 'DOING_AJAX' ) || 'POST' == $_SERVER['REQUEST_METHOD'] ) {
$expired += HOUR_IN_SECONDS;
}
// Quick check to see if an honest cookie has expired
if ( $expired < time() ) {
$this->lastStatusMsg = SwpmUtils::_( 'Session Expired.' ); //do_action('auth_cookie_expired', $cookie_elements);
SwpmLog::log_auth_debug( 'validate() - Session Expired', true );
return false;
}
global $wpdb;
$query = ' SELECT * FROM ' . $wpdb->prefix . 'swpm_members_tbl WHERE user_name = %s';
$user = $wpdb->get_row( $wpdb->prepare( $query, $username ) );
if ( empty( $user ) ) {
$this->lastStatusMsg = SwpmUtils::_( 'Invalid Username' );
return false;
}
$pass_frag = substr( $user->password, 8, 4 );
$key = self::b_hash( $username . $pass_frag . '|' . $expiration );
$hash = hash_hmac( 'md5', $username . '|' . $expiration, $key );
if ( $hmac != $hash ) {
$this->lastStatusMsg = SwpmUtils::_( 'Please login again.' );
SwpmLog::log_auth_debug( 'validate() - Bad Hash', true );
do_action('swpm_validate_login_hash_mismatch');
wp_logout(); //Force logout of WP user session to clear the bad hash.
return false;
}
if ( $expiration < time() ) {
$GLOBALS['login_grace_period'] = 1;
}
$this->userData = $user;
return $this->check_constraints();
}
public static function b_hash( $data, $scheme = 'auth' ) {
$salt = wp_salt( $scheme ) . 'j4H!B3TA,J4nIn4.';
return hash_hmac( 'md5', $data, $salt );
}
public function is_logged_in() {
return $this->isLoggedIn;
}
public function get( $key, $default = '' ) {
if ( isset( $this->userData->$key ) ) {
return $this->userData->$key;
}
if ( isset( $this->permitted->$key ) ) {
return $this->permitted->$key;
}
if ( ! empty( $this->permitted ) ) {
return $this->permitted->get( $key, $default );
}
return $default;
}
public function get_message() {
return $this->lastStatusMsg;
}
public function get_expire_date() {
if ( $this->isLoggedIn ) {
return SwpmUtils::get_formatted_expiry_date( $this->get( 'subscription_starts' ), $this->get( 'subscription_period' ), $this->get( 'subscription_duration_type' ) );
}
return '';
}
public function delete() {
if ( ! $this->is_logged_in() ) {
return;
}
$user_name = $this->get( 'user_name' );
$user_id = $this->get( 'member_id' );
$subscr_id = $this->get( 'subscr_id' );
$email = $this->get( 'email' );
$this->logout();
wp_clear_auth_cookie();
SwpmMembers::delete_swpm_user_by_id( $user_id );
SwpmMembers::delete_wp_user( $user_name );
}
public function reload_user_data() {
if ( ! $this->is_logged_in() ) {
return;
}
global $wpdb;
$query = 'SELECT * FROM ' . $wpdb->prefix . 'swpm_members_tbl WHERE member_id = %d';
$this->userData = $wpdb->get_row( $wpdb->prepare( $query, $this->userData->member_id ) );
}
public function is_expired_account() {
if ( ! $this->is_logged_in() ) {
return null;
}
$account_status = $this->get( 'account_state' );
if ( $account_status == 'expired' || $account_status == 'inactive' ) {
//Expired or Inactive accounts are both considered to be expired.
return true;
}
return false;
}
}