1647 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1647 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/*********************************************************************************
 | 
						|
 *
 | 
						|
 * TimeTrex is a Workforce Management program developed by
 | 
						|
 * TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify it under
 | 
						|
 * the terms of the GNU Affero General Public License version 3 as published by
 | 
						|
 * the Free Software Foundation with the addition of the following permission
 | 
						|
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 | 
						|
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
 | 
						|
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 | 
						|
 * details.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU Affero General Public License along
 | 
						|
 * with this program; if not, see http://www.gnu.org/licenses or write to the Free
 | 
						|
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
						|
 * 02110-1301 USA.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
 | 
						|
 * #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * The interactive user interfaces in modified source and object code versions
 | 
						|
 * of this program must display Appropriate Legal Notices, as required under
 | 
						|
 * Section 5 of the GNU Affero General Public License version 3.
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * In accordance with Section 7(b) of the GNU Affero General Public License
 | 
						|
 * version 3, these Appropriate Legal Notices must retain the display of the
 | 
						|
 * "Powered by TimeTrex" logo. If the display of the logo is not reasonably
 | 
						|
 * feasible for technical reasons, the Appropriate Legal Notices must display
 | 
						|
 * the words "Powered by TimeTrex".
 | 
						|
 *
 | 
						|
 ********************************************************************************/
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * @package API\UnAuthenticated
 | 
						|
 */
 | 
						|
class APIAuthentication extends APIFactory {
 | 
						|
	protected $main_class = 'Authentication';
 | 
						|
 | 
						|
	/**
 | 
						|
	 * APIAuthentication constructor.
 | 
						|
	 */
 | 
						|
	public function __construct() {
 | 
						|
		parent::__construct(); //Make sure parent constructor is always called.
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param null $user_name
 | 
						|
	 * @param null $password
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function PunchLogin( $user_name = null, $password = null ) {
 | 
						|
		global $config_vars, $authentication;
 | 
						|
		Debug::Text( 'Quick Punch ID: ' . $user_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		if ( ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] == 1 ) || ( isset( $config_vars['other']['down_for_maintenance'] ) && $config_vars['other']['down_for_maintenance'] == 1 ) ) {
 | 
						|
			Debug::text( 'WARNING: Installer is enabled... Normal logins are disabled!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			//When installer is enabled, just display down for maintenance message to user if they try to login.
 | 
						|
			$error_message = TTi18n::gettext( '%1 is currently undergoing maintenance. We apologize for any inconvenience this may cause, please try again later. (%2)', [ APPLICATION_NAME, ( ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] == 1 ) ? 'INSTALL' : 'MAINT' ) ] );
 | 
						|
			$validator_obj = new Validator();
 | 
						|
			$validator_stats = [ 'total_records' => 1, 'valid_records' => 0 ];
 | 
						|
			$validator_obj->isTrue( 'user_name', false, $error_message );
 | 
						|
			$validator = [];
 | 
						|
			$validator[0] = $validator_obj->getErrorsArray();
 | 
						|
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $validator, $validator_stats );
 | 
						|
		}
 | 
						|
		if ( isset( $config_vars['other']['web_session_expire'] ) && $config_vars['other']['web_session_expire'] != '' ) {
 | 
						|
			$authentication->setEnableExpireSession( (int)$config_vars['other']['web_session_expire'] );
 | 
						|
		}
 | 
						|
		$clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */
 | 
						|
		$clf->getByPhoneID( $user_name );
 | 
						|
		if ( $clf->getRecordCount() == 1 ) {
 | 
						|
			$c_obj = $clf->getCurrent();
 | 
						|
		} else {
 | 
						|
			$c_obj = false;
 | 
						|
		}
 | 
						|
 | 
						|
		//Checks user_name/password
 | 
						|
		$password_result = false;
 | 
						|
		$user_name = trim( $user_name );
 | 
						|
		if ( $user_name != '' && $password != '' && ( is_object( $c_obj ) && in_array( $c_obj->getStatus(), [ 10, 20 ] ) && getTTProductEdition() >= TT_PRODUCT_PROFESSIONAL && $c_obj->getProductEdition() >= TT_PRODUCT_PROFESSIONAL ) ) { //Allow QuickPunch logins when company is on hold so employees can still punch in/out.
 | 
						|
			$password_result = $authentication->Login( $user_name, $password, 'QUICK_PUNCH_ID' );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $password_result === true ) {
 | 
						|
			$clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */
 | 
						|
			$clf->getByID( $authentication->getObject()->getCompany() );
 | 
						|
			$current_company = $clf->getCurrent();
 | 
						|
			unset( $clf );
 | 
						|
			$create_new_station = false;
 | 
						|
			//If this is a new station, insert it now.
 | 
						|
			if ( isset( $_COOKIE['StationID'] ) ) {
 | 
						|
				Debug::text( 'Station ID Cookie found! ' . $_COOKIE['StationID'], __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
				$slf = TTnew( 'StationListFactory' ); /** @var StationListFactory $slf */
 | 
						|
				$slf->getByStationIdandCompanyId( $_COOKIE['StationID'], $current_company->getId() );
 | 
						|
				$current_station = $slf->getCurrent();
 | 
						|
				unset( $slf );
 | 
						|
 | 
						|
				if ( $current_station->isNew() ) {
 | 
						|
					Debug::text( 'Station ID is NOT IN DB!! ' . $_COOKIE['StationID'], __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$create_new_station = true;
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				$create_new_station = true;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( $create_new_station == true ) {
 | 
						|
				//Insert new station
 | 
						|
				$sf = TTnew( 'StationFactory' ); /** @var StationFactory $sf */
 | 
						|
 | 
						|
				$sf->setCompany( $current_company->getId() );
 | 
						|
				$sf->setStatus( 20 ); //Enabled
 | 
						|
				if ( Misc::detectMobileBrowser() == false ) {
 | 
						|
					Debug::text( 'PC Station device...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$sf->setType( 10 ); //PC
 | 
						|
				} else {
 | 
						|
					$sf->setType( 26 ); //Mobile device web browser
 | 
						|
					Debug::text( 'Mobile Station device...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				}
 | 
						|
				$sf->setSource( Misc::getRemoteIPAddress() );
 | 
						|
				$sf->setStation();
 | 
						|
				$sf->setDescription( substr( $_SERVER['HTTP_USER_AGENT'], 0, 250 ) );
 | 
						|
				if ( $sf->isValid() ) { //Standard Edition can't save mobile stations.
 | 
						|
					if ( $sf->Save( false ) ) {
 | 
						|
						$sf->setCookie();
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			return [ 'SessionID' => $authentication->getSessionId() ];
 | 
						|
		} else {
 | 
						|
			$validator_obj = new Validator();
 | 
						|
			$validator_stats = [ 'total_records' => 1, 'valid_records' => 0 ];
 | 
						|
 | 
						|
			$error_column = 'quick_punch_id'; // match the correct input field in the html
 | 
						|
			$error_message = TTi18n::gettext( 'Quick Punch ID or Password is incorrect' );
 | 
						|
			//Get company status from user_name, so we can display messages for ONHOLD/Cancelled accounts.
 | 
						|
			if ( is_object( $c_obj ) ) {
 | 
						|
				//Allow QuickPunch when company is ON HOLD.
 | 
						|
//				if ( $c_obj->getStatus() == 20 ) {
 | 
						|
//					$error_message = TTi18n::gettext('Sorry, your company\'s account has been placed ON HOLD, please contact customer support immediately');
 | 
						|
//				} else
 | 
						|
				if ( $c_obj->getStatus() == 23 ) {
 | 
						|
					$error_message = TTi18n::gettext( 'Sorry, your trial period has expired, please contact our sales department to reactivate your account' );
 | 
						|
				} else if ( $c_obj->getStatus() == 28 ) {
 | 
						|
					if ( $c_obj->getMigrateURL() != '' ) {
 | 
						|
						$error_message = TTi18n::gettext( 'To better serve our customers your account has been migrated, please update your bookmarks to use the following URL from now on' ) . ': ' . 'http://' . $c_obj->getMigrateURL();
 | 
						|
					} else {
 | 
						|
						$error_message = TTi18n::gettext( 'To better serve our customers your account has been migrated, please contact customer support immediately.' );
 | 
						|
					}
 | 
						|
				} else if ( $c_obj->getStatus() == 30 ) {
 | 
						|
					$error_message = TTi18n::gettext( 'Sorry, your company\'s account has been CANCELLED, please contact customer support if you believe this is an error' );
 | 
						|
				}
 | 
						|
			}
 | 
						|
			$validator_obj->isTrue( $error_column, false, $error_message );
 | 
						|
			$validator = [];
 | 
						|
			$validator[0] = $validator_obj->getErrorsArray();
 | 
						|
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $validator, $validator_stats );
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param $user_name
 | 
						|
	 * @return array|bool
 | 
						|
	 */
 | 
						|
	function getSessionTypeForLogin( $user_name ) {
 | 
						|
		global $config_vars;
 | 
						|
 | 
						|
		$retval = [
 | 
						|
				'session_type' => 'user_name',
 | 
						|
				'mfa_type_id'  => 0,
 | 
						|
		];
 | 
						|
 | 
						|
		$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
		$ulf->getByUserName( $user_name );
 | 
						|
		if ( $ulf->getRecordCount() == 1 && $ulf->getCurrent()->getMultiFactorType() > 0 && getTTProductEdition() >= TT_PRODUCT_PROFESSIONAL ) { //0 = Disabled
 | 
						|
			if ( isset( $config_vars['other']['disable_mfa'] ) && $config_vars['other']['disable_mfa'] == true ) {
 | 
						|
				//Login is forced to use user_name/password only when MFA is disabled.
 | 
						|
				Debug::Text( 'MFA is disabled. User has MFA enabled but the session has been forced to user_name (800)', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			} else {
 | 
						|
				$retval['session_type'] = 'user_name_multi_factor';
 | 
						|
				$retval['mfa_type_id'] = $ulf->getCurrent()->getMultiFactorType();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( $retval );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array|bool
 | 
						|
	 */
 | 
						|
	function sendMultiFactorNotification() {
 | 
						|
		$authentication = new Authentication();
 | 
						|
		$authentication->setAndReadForMultiFactor();
 | 
						|
 | 
						|
		$multi_factor_data = $authentication->getMultiFactorData();
 | 
						|
 | 
						|
		if ( $multi_factor_data == false ) {
 | 
						|
			Debug::Text( 'No multifactor data found for session ID: ' . $authentication->getSessionId(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			return $this->returnHandler( false );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $multi_factor_data['time'] === '' || ( TTDate::getTime() - $multi_factor_data['time'] ) > $authentication->mfa_timeout_seconds ) {
 | 
						|
			Debug::Text( 'Multifactor data is expired, not sending notification', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			return $this->returnHandler( false );
 | 
						|
		}
 | 
						|
 | 
						|
		$user_agent = null;
 | 
						|
		$user_agent_display = null;
 | 
						|
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
 | 
						|
			require_once( Environment::getBasePath() . 'vendor' . DIRECTORY_SEPARATOR . 'cbschuld' . DIRECTORY_SEPARATOR . 'browser.php' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Browser.php' );
 | 
						|
			$browser = new Browser( $_SERVER['HTTP_USER_AGENT'] );
 | 
						|
			$user_agent = $browser->getBrowser();
 | 
						|
			if ( $user_agent === 'unknown' ) {
 | 
						|
				$user_agent_display = $_SERVER['HTTP_USER_AGENT'];
 | 
						|
			} else {
 | 
						|
				$agent_arr = [];
 | 
						|
 | 
						|
				if ( $browser->getBrowser() !== 'unknown' ) {
 | 
						|
					$agent_arr[] = $browser->getBrowser();
 | 
						|
				}
 | 
						|
 | 
						|
				if ( $browser->getVersion() !== 'unknown' ) {
 | 
						|
					$agent_arr[] = 'v' . $browser->getVersion();
 | 
						|
				}
 | 
						|
 | 
						|
				if ( $browser->getPlatform() !== 'unknown' ) {
 | 
						|
					$agent_arr[] = TTi18n::getText( 'on ' ) . $browser->getPlatform();
 | 
						|
				}
 | 
						|
 | 
						|
				$user_agent_display = implode( ' ', $agent_arr );
 | 
						|
			}
 | 
						|
			unset( $browser );
 | 
						|
		}
 | 
						|
 | 
						|
		$payload = [
 | 
						|
				'timetrex' => [
 | 
						|
						'event' => [
 | 
						|
								[
 | 
						|
										'type' => 'multi_factor_authenticate',
 | 
						|
										'data' => [
 | 
						|
												'user_id'                 => $multi_factor_data['user_id'],
 | 
						|
												'session_id'              => $multi_factor_data['session_id'],
 | 
						|
												'key'                     => $multi_factor_data['key'],
 | 
						|
												'login_timestamp'         => TTDate::getTime(),
 | 
						|
												'login_timestamp_display' => TTDate::getDate( 'DATE+TIME', TTDate::getTime() ),
 | 
						|
												'user_agent'              => $user_agent,
 | 
						|
												'user_agent_display'      => $user_agent_display,
 | 
						|
												'ip_address'              => Misc::getRemoteIPAddress(),
 | 
						|
												'location'                => Misc::getLocationOfIPAddress(),
 | 
						|
										],
 | 
						|
								],
 | 
						|
						],
 | 
						|
				],
 | 
						|
				'uri'      => 'multi_factor_authenticate', //Required for app to trigger proper event on its side.
 | 
						|
		];
 | 
						|
 | 
						|
		Debug::Arr( $payload, '  Sending background notification to users app to authenticate multifactor...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		$notification_data = [
 | 
						|
				'object_id'         => $authentication->getSessionId(),
 | 
						|
				'user_id'           => $multi_factor_data['user_id'],
 | 
						|
				'type_id'           => 'system',
 | 
						|
				'object_type_id'    => 0,
 | 
						|
				'priority'          => 1, //1=Highest
 | 
						|
				'title_short'       => TTi18n::getText( 'Verify your identity' ),
 | 
						|
				'body_short'        => TTi18n::getText( 'Are you logging into TimeTrex currently?' ),
 | 
						|
				'payload'           => $payload,
 | 
						|
				'time_to_live'      => $authentication->mfa_timeout_seconds,
 | 
						|
				'device_id'         => [ 32768 ], //App Only.
 | 
						|
				'save_notification' => false,
 | 
						|
		];
 | 
						|
		Notification::sendNotification( $notification_data );
 | 
						|
 | 
						|
		return $this->returnHandler( true );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Starts a 30 second listen loop waiting for a multifactor authentication response from validateMultiFactor().
 | 
						|
	 * This loop runs for the device trying to log and that waits for a multifactor approval from another device or API request.
 | 
						|
	 * @return bool
 | 
						|
	 * @throws DBError
 | 
						|
	 */
 | 
						|
	public function listenForMultiFactorAuthentication( $enable_trusted_device = false ) {
 | 
						|
		global $config_vars, $db, $cache;
 | 
						|
 | 
						|
		// When using memoryCaching, this long-running process will never get an updated cache from a different process during the listen loop.
 | 
						|
		// But if technically caching is *always* enabled, even when disabled we switch to memoryCaching only mode.
 | 
						|
		// So when caching is disabled in the .ini, disable it fully.
 | 
						|
		// When caching is enabled, just disable memoryCaching, so cache invalidations can be picked up in this long-running process.
 | 
						|
		// Note: $authentication->setAndReadForMultiFactor() already skips cache. But keeping this here as a safety net.
 | 
						|
		if ( !isset( $config_vars['cache']['enable'] ) || $config_vars['cache']['enable'] == false ) {
 | 
						|
			$cache->_caching = false;
 | 
						|
		}
 | 
						|
		$cache->_memoryCaching = false;
 | 
						|
 | 
						|
		Debug::Text( 'Multifactor enabled! Listening for auth response.', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		$config_vars['database']['persistent_connections'] = true; //Force persistent connections so LISTEN/NOTIFY works properly.
 | 
						|
 | 
						|
		$authentication = new Authentication();
 | 
						|
		$authentication->setAndReadForMultiFactor();
 | 
						|
		$multi_factor_data = $authentication->getMultiFactorData();
 | 
						|
		if ( $multi_factor_data == false ) {
 | 
						|
			Debug::Text( 'No multifactor data found for session ID: ' . $authentication->getSessionID(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			return $this->returnHandler( [ 'status' => 'cancelled' ] );
 | 
						|
		} else {
 | 
						|
			Debug::Arr( $multi_factor_data, ' Initial Multifactor data for Session ID: ' . $authentication->getSessionID(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( isset( $multi_factor_data['time'] ) && ( TTDate::getTime() - $multi_factor_data['time'] < $authentication->mfa_timeout_seconds ) ) {
 | 
						|
			//If current multifactor data is less than X minutes old and already authenticated, we not need to start a listen.
 | 
						|
			if ( isset( $multi_factor_data['login_approved'] ) == true && $multi_factor_data['login_approved'] == true ) {
 | 
						|
				Debug::Text( 'Multifactor data is already authenticated, not starting listen', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				//login_approved is a one time flag to know if this session has already been authenticated, and if it has, we don't need to start a listen. The flag can now be cleared so future listen requests can start a new listen.
 | 
						|
				$authentication->clearMultiFactorDataFlags( [ 'login_approved' ] );
 | 
						|
 | 
						|
				return $this->returnHandler( [ 'status' => 'completed' ] );
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		Debug::text( '  Listening for Multifactor authentication. Started at: ' . TTDate::getTime(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		$db->Execute( 'LISTEN "multi_auth:' . $db->qStr( TTUUID::castUUID( $multi_factor_data['user_id'] ) ) . '"' );
 | 
						|
		$i = 0;
 | 
						|
 | 
						|
		while ( $i < 30 ) {
 | 
						|
			$_SERVER['REQUEST_TIME_FLOAT'] = microtime( true ); //This restarts the DEBUG time on each loop as if its a separate request. This helps prevent long request WARNING from being triggered like in reports.
 | 
						|
 | 
						|
			$auth_status = pg_get_notify( $db->_connectionID );
 | 
						|
			if ( isset( $auth_status['payload'] ) && $auth_status['payload'] != '' ) {
 | 
						|
				$mfa_response_payload = json_decode( $auth_status['payload'], true );
 | 
						|
 | 
						|
				if ( $mfa_response_payload['success'] == true ) {
 | 
						|
					Debug::Text( ' PG Notify Auth Payload Received: ' . $auth_status['payload'], __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					if ( $mfa_response_payload['key'] == $multi_factor_data['expected_key'] ) {
 | 
						|
						//Can continue with login.
 | 
						|
						Debug::text( '  SUCCESS: Notify auth request accepted, login allowed. Received at: ' . TTDate::getTime(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
						//Authenticaiton may have outdated data depending on how long listen has been running. Need to re-read it.
 | 
						|
						$authentication = new Authentication();
 | 
						|
						$authentication->setAndReadForMultiFactor();
 | 
						|
 | 
						|
						//Authentication may have outdated data depending on how long listen has been running. We may need to re-read it.
 | 
						|
						//  **NOTE: When using database replication, its possible this data could still be out-dated. So we need to make sure we aren't writing any data to the authentication table here.
 | 
						|
						//          If we do, then it should be wrapped in a transaction so its always reads/writes to/from the master database node.
 | 
						|
 | 
						|
						if ( $enable_trusted_device == true ) {
 | 
						|
							$authentication->setTrustedDevice();
 | 
						|
						}
 | 
						|
 | 
						|
						return $this->returnHandler( [ 'status' => 'completed' ] );
 | 
						|
					} else {
 | 
						|
						Debug::Text( '  FAILURE: Notify auth request failed as keys did not match, login denied', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
						return $this->returnHandler( [ 'status' => 'cancelled' ] );
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					if ( isset( $mfa_response_payload['other_data']['restart_listen'] ) && $mfa_response_payload['other_data']['restart_listen'] == true ) {
 | 
						|
						Debug::text( '  Listen cancelled early as a restart listen request was received...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
						return $this->returnHandler( [ 'status' => 'restart_listen' ] );
 | 
						|
					}
 | 
						|
 | 
						|
					Debug::Text( '  FAILURE: Notify auth request denied, login denied', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
					return $this->returnHandler( [ 'status' => 'cancelled' ] );
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			$i++;
 | 
						|
			sleep( 1 );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( [ 'status' => 'restart_listen' ] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * This is called by the app/device that approves or denies the multi-factor authentication request.
 | 
						|
	 * It then sends a SQL Notify message to listenForMultiFactorAuthentication() to let it know if the request was approved or denied.
 | 
						|
	 * @param $validation_allowed bool
 | 
						|
	 * @param $session_id string
 | 
						|
	 * @param $key string
 | 
						|
	 * @param null $other_data
 | 
						|
	 * @return array|bool
 | 
						|
	 * @throws DBError
 | 
						|
	 */
 | 
						|
	public function validateMultiFactor( $validation_allowed, $session_id, $key, $other_data = null ) {
 | 
						|
		global $db;
 | 
						|
 | 
						|
		Debug::Text( 'Attempting to validate multifactor.', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		Debug::Text( 'App sent a validation allowed response of: ' . Misc::HumanBoolean( $validation_allowed ), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		$authentication = new Authentication();
 | 
						|
		$authentication->setAndReadForMultiFactor( $session_id );
 | 
						|
 | 
						|
		$multi_factor_data = $authentication->getMultiFactorData();
 | 
						|
		$other_json = $authentication->getOtherJSON();
 | 
						|
 | 
						|
		if ( $multi_factor_data !== false ) { //There is a pending multinfactor auth request.
 | 
						|
			Debug::Arr( $multi_factor_data, 'Multifactor data: ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			Debug::Arr( $other_json, 'Other JSON data: ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			$error_message = null;
 | 
						|
 | 
						|
			//App neede to be logged in to validate a session and the user_id needs to match for both sessions.
 | 
						|
			if ( ( $this->isLoggedIn() == false || $this->getCurrentUserObject()->getId() != $multi_factor_data['user_id'] ) ) {
 | 
						|
				Debug::Text( '  FAILURE: Session is not allowed to be validated, as the session attempting to validate is not logged in or user mismatch.', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				$validation_allowed = false; //Not allowed to validate if the app is not logged in or users mismatch.
 | 
						|
 | 
						|
				//If we are requesting listen to be restarted for any reason, we do not need to send a validation error for user not being logged in.
 | 
						|
				//This is because there is a legitimate scenario where the user is not yet logged in but the listen needs to be restarted.
 | 
						|
				if ( isset( $other_data['restart_listen'] ) == false || $other_data['restart_listen'] == false ) {
 | 
						|
					//Restart listen was not requested or is false, so send a validation error that the user is not logged in.
 | 
						|
					$error_message = TTi18n::getText( 'Unable to validate identity, please login and try again.' ); //Session is not allowed to be validated, as the session attempting to validate is not logged in or user mismatch.
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if ( ( $key !== $multi_factor_data['expected_key'] || $multi_factor_data['expected_key'] == false ) ) {
 | 
						|
				Debug::Text( '  FAILURE: Session is not allowed to be validated, as the key does not match or is invalid.', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				$error_message = TTi18n::getText( 'Unable to validate identity due to key mismatch, please try again.' ); //Session is not allowed to be validated, there was a authentication mismatch.
 | 
						|
				$validation_allowed = false;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( ( $multi_factor_data['time'] == false || ( TTDate::getTime() - $multi_factor_data['time'] ) > $authentication->mfa_timeout_seconds ) ) {
 | 
						|
				Debug::Text( '  FAILURE: Session is not allowed to be validated, as the the multifactor attempt has expired.', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				$error_message = TTi18n::getText( 'Identity validation request has expired, please login again.' ); //Session is not allowed to be validated, as the the multifactor attempt has expired.
 | 
						|
				$validation_allowed = false;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( $validation_allowed == true ) {
 | 
						|
				//If login is already approved we do not reauthenticate or modify the session as it already has been authenticated.
 | 
						|
				if ( ( isset( $other_json['mfa']['login_approved'] ) == false || $other_json['mfa']['login_approved'] == false ) ) {
 | 
						|
					Debug::text( 'Sending multifactor response: SUCCESS', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$other_json['mfa']['login_approved'] = true; //One time flag for API to check before starting listen, incase it was already approved.
 | 
						|
					$other_json['mfa']['recent_authenticated_device_session_id'] = $authentication->encryptSessionID( $authentication->getCurrentSessionID( 800 ) ); //Store the session ID of the device that just authenticated this request.
 | 
						|
					$authentication->setOtherJSON( $other_json );
 | 
						|
 | 
						|
					if ( $multi_factor_data['is_reauthentication_request'] === true ) { //This may just be a setup test or reauthentication, we do not want to upgrade the session type.
 | 
						|
						$authentication->setReauthenticatedSession();
 | 
						|
						TTLog::addEntry( $authentication->getObjectID(), 102, TTi18n::getText( 'From' ) . ': ' . Misc::getLocationOfIPAddress( $authentication->getIPAddress() ) . ' (' . $authentication->getIPAddress() . ') ' . TTi18n::getText( 'Type' ) . ': user_name_multi_factor ' . TTi18n::getText( 'SessionID' ) . ': ' . $authentication->getSecureSessionID() . ' ' . TTi18n::getText( 'Client' ) . ': ' . $authentication->getClientID() . ' ' . TTi18n::getText( 'End Point' ) . ': ' . $authentication->getEndPointID() . ' ' . TTi18n::getText( 'ObjectID' ) . ': ' . $authentication->getObjectID(), $authentication->getObjectID(), 'authentication' ); //ReAuthenticate
 | 
						|
					} else if ( $multi_factor_data['type_id'] == 0 || $multi_factor_data['type_id'] == 800 ) {
 | 
						|
						$authentication->setType( 810 );
 | 
						|
 | 
						|
						//This updates the user table and not authentication table.
 | 
						|
						$authentication->UpdateLastLoginDate( $multi_factor_data['user_id'] );
 | 
						|
 | 
						|
						//Login is fully successful, so add "Login" audit log entry.
 | 
						|
						TTLog::addEntry( $authentication->getObjectID(), 100, TTi18n::getText( 'From' ) . ': ' . Misc::getLocationOfIPAddress( $authentication->getIPAddress() ) . ' (' . $authentication->getIPAddress() . ') ' . TTi18n::getText( 'Type' ) . ': user_name_multi_factor ' . TTi18n::getText( 'SessionID' ) . ': ' . $authentication->getSecureSessionID() . ' ' . TTi18n::getText( 'Client' ) . ': ' . $authentication->getClientID() . ' ' . TTi18n::getText( 'End Point' ) . ': ' . $authentication->getEndPointID() . ' ' . TTi18n::getText( 'ObjectID' ) . ': ' . $authentication->getObjectID(), $authentication->getObjectID(), 'authentication' ); //Login
 | 
						|
					} else {
 | 
						|
						Debug::text( 'Not modifying session, as not reauthenticating and not valid Type ID to promote: ' . $multi_factor_data['type_id'], __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					}
 | 
						|
 | 
						|
					$authentication->clearMultiFactorDataFlags( [ 'key' ] ); //Key has been consumed and is no longer valid and cannot be re-used.
 | 
						|
				} else {
 | 
						|
					Debug::text( 'Login has alrady been approved, not updating session...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				Debug::text( 'NOTICE: $validation_allowed is false, not updating session!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			}
 | 
						|
 | 
						|
			$response = [
 | 
						|
					'success'    => (bool)$validation_allowed,
 | 
						|
					'key'        => preg_replace( '/[^a-zA-Z0-9]/', '', (string)$key ), //Sanitizing the key to only contain alphanumeric characters to help prevent SQL injection.
 | 
						|
					'other_data' => [],
 | 
						|
			];
 | 
						|
 | 
						|
			//Other data is optional but can contain further information/commands for various multifactor types.
 | 
						|
			//It contains only specific data and does not allow arbitrary data to be passed in.
 | 
						|
			if ( isset( $other_data['restart_listen'] ) && $other_data['restart_listen'] == true ) {
 | 
						|
				$response['other_data']['restart_listen'] = true;
 | 
						|
			}
 | 
						|
 | 
						|
			Debug::Arr( $response, 'Sending multifactor notify message.', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			$db->Execute( 'NOTIFY "multi_auth:' . $db->qStr( TTUUID::castUUID( $multi_factor_data['user_id'] ) ) . '", ' . $db->qStr( json_encode( $response ) ) );
 | 
						|
 | 
						|
			if ( $error_message == null ) {
 | 
						|
				return $this->returnHandler( true );
 | 
						|
			} else {
 | 
						|
				return $this->returnHandler( false, 'VALIDATION', $error_message );
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			Debug::text( 'No multifactor data for session...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'Failed to validate multifactor as the request may have expired.' ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Default username=NULL to prevent argument warnings messages if its not passed from the API.
 | 
						|
	 * @param null $user_name
 | 
						|
	 * @param null $password
 | 
						|
	 * @param string $type
 | 
						|
	 * @param bool $reauthenticate_only
 | 
						|
	 * @return array|bool
 | 
						|
	 */
 | 
						|
	function Login( $user_name = null, $password = null, $type = 'USER_NAME', $reauthenticate_only = false ) {
 | 
						|
		global $config_vars, $authentication;
 | 
						|
 | 
						|
		//Prevent: NOTICE(8): Array to string conversion below.
 | 
						|
		if ( is_array( $user_name ) ) {
 | 
						|
			$user_name = '';
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_array( $password ) ) {
 | 
						|
			$password = '';
 | 
						|
		}
 | 
						|
 | 
						|
		Debug::text( 'User Name: ' . $user_name . ' Password Length: ' . strlen( $password ) . ' Type: ' . $type, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		if ( ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] == 1 ) || ( isset( $config_vars['other']['down_for_maintenance'] ) && $config_vars['other']['down_for_maintenance'] == 1 ) ) {
 | 
						|
			Debug::text( 'WARNING: Installer is enabled... Normal logins are disabled!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			//When installer is enabled, just display down for maintenance message to user if they try to login.
 | 
						|
			$error_message = TTi18n::gettext( '%1 is currently undergoing maintenance. We apologize for any inconvenience this may cause, please try again later. (%2)', [ APPLICATION_NAME, ( ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] == 1 ) ? 'INSTALL' : 'MAINT' ) ] );
 | 
						|
			$validator_obj = new Validator();
 | 
						|
			$validator_stats = [ 'total_records' => 1, 'valid_records' => 0 ];
 | 
						|
			$validator_obj->isTrue( 'user_name', false, $error_message );
 | 
						|
			$validator = [];
 | 
						|
			$validator[0] = $validator_obj->getErrorsArray();
 | 
						|
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $validator, $validator_stats );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( isset( $config_vars['other']['web_session_expire'] ) && $config_vars['other']['web_session_expire'] != '' ) {
 | 
						|
			$authentication->setEnableExpireSession( (int)$config_vars['other']['web_session_expire'] );
 | 
						|
		}
 | 
						|
 | 
						|
		$login_method = $this->stripReturnHandler( $this->getSessionTypeForLogin( $user_name ) );
 | 
						|
 | 
						|
		if ( strtolower( $type ) === 'user_name' ) {
 | 
						|
			//Don't allow users to circumvent the multifactor auth process, by forcing 'user_name' login type when MFA is enabled.
 | 
						|
			$type = $login_method['session_type'];
 | 
						|
		}
 | 
						|
 | 
						|
		if ( strtolower( $type ) === 'user_name_multi_factor' && $authentication->getClientIDHeader() == 'App-TimeTrex' && Misc::getMobileAppClientVersion() != '' && version_compare( Misc::getMobileAppClientVersion(), '5.1.10', '<' ) == true ) {
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'Please upgrade to a newer version of the app that supports multifactor authentication.' ) );
 | 
						|
		}
 | 
						|
 | 
						|
		//Only allow reauthentication for the currently logged-in user.
 | 
						|
		if ( $reauthenticate_only && ( $this->isLoggedIn() === false || $this->getCurrentUserObject()->checkUsername( $user_name ) === false ) ) {
 | 
						|
			$validator_obj = new Validator();
 | 
						|
			$validator_obj->isTrue( 'user_name', false, TTi18n::gettext( 'Authenticating for invalid employee.' ) );
 | 
						|
			$validator_stats = [ 'total_records' => 1, 'valid_records' => 0 ];
 | 
						|
			$validator[0] = $validator_obj->getErrorsArray();
 | 
						|
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $validator, $validator_stats );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $authentication->Login( $user_name, $password, $type, true, $reauthenticate_only, $login_method['mfa_type_id'] ) === true ) {
 | 
						|
 | 
						|
			if ( strtolower( $type ) === 'user_name_multi_factor' ) {
 | 
						|
				Debug::text( 'MFA Login Success, Session ID: ' . $authentication->getSessionId(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
				$authentication->generateMultiFactorAuthKey( $reauthenticate_only );
 | 
						|
 | 
						|
				return $this->returnHandler( $authentication->getAuthenticationResponseData( TTi18n::getText( 'Please check your device to verify your identity' ) ) );
 | 
						|
			} else {
 | 
						|
				Debug::text( 'Login Success, Session ID: ' . $authentication->getSessionId(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
				//Return different data stucture to end-points that support MFA. Only Browser and Mobile App >=5.1.10 for now.
 | 
						|
				if ( $authentication->getClientIDHeader() == 'Browser-TimeTrex' || ( $authentication->getClientIDHeader() == 'App-TimeTrex' && Misc::getMobileAppClientVersion() != '' && version_compare( Misc::getMobileAppClientVersion(), '5.1.10', '>=' ) == true ) ) {
 | 
						|
					if ( $reauthenticate_only == true ) {
 | 
						|
						$authentication->setReauthenticatedSession( true );
 | 
						|
					}
 | 
						|
 | 
						|
					return $this->returnHandler( $authentication->getAuthenticationResponseData() );
 | 
						|
				} else {
 | 
						|
					Debug::text( '  Returning raw Session ID to legacy API/App: ' . $authentication->getSessionId(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					return $authentication->getSessionId(); //Legacy app versions don't support MFA, so just return the session ID.
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			$validator_obj = new Validator();
 | 
						|
			$validator_stats = [ 'total_records' => 1, 'valid_records' => 0 ];
 | 
						|
 | 
						|
			$error_column = 'user_name';
 | 
						|
			if ( $reauthenticate_only == true ) {
 | 
						|
				$error_message = TTi18n::gettext( 'Password is incorrect' );
 | 
						|
			} else {
 | 
						|
				$error_message = TTi18n::gettext( 'User Name or Password is incorrect' );
 | 
						|
			}
 | 
						|
 | 
						|
			//Get company status from user_name, so we can display messages for ONHOLD/Cancelled accounts.
 | 
						|
			$clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */
 | 
						|
			$clf->getByUserName( $user_name );
 | 
						|
			if ( $clf->getRecordCount() > 0 ) {
 | 
						|
				$c_obj = $clf->getCurrent();
 | 
						|
				if ( $c_obj->getStatus() == 20 ) {
 | 
						|
					$error_message = TTi18n::gettext( 'Sorry, your company\'s account has been placed ON HOLD, please contact customer support immediately' );
 | 
						|
				} else if ( $c_obj->getStatus() == 23 ) {
 | 
						|
					$error_message = TTi18n::gettext( 'Sorry, your trial period has expired, please contact our sales department to reactivate your account' );
 | 
						|
				} else if ( $c_obj->getStatus() == 28 ) {
 | 
						|
					if ( $c_obj->getMigrateURL() != '' ) {
 | 
						|
						$migrate_url = ( Misc::isSSL() == true ) ? 'https://' . $c_obj->getMigrateURL() : 'http://' . $c_obj->getMigrateURL();
 | 
						|
						$error_message = TTi18n::gettext( 'To better serve our customers your account has been migrated, please update your bookmarks to use the following URL from now on' ) . ': ' . '<a href="' . $migrate_url . '">' . $migrate_url . '</a>';
 | 
						|
					} else {
 | 
						|
						$error_message = TTi18n::gettext( 'To better serve our customers your account has been migrated, please contact customer support immediately.' );
 | 
						|
					}
 | 
						|
				} else if ( $c_obj->getStatus() == 30 ) {
 | 
						|
					$error_message = TTi18n::gettext( 'Sorry, your company\'s account has been CANCELLED, please contact customer support if you believe this is an error' );
 | 
						|
				} else {
 | 
						|
					$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
					$ulf->getByUserName( $user_name );
 | 
						|
					if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
						$u_obj = $ulf->getCurrent(); /** @var UserFactory $u_obj */
 | 
						|
 | 
						|
						if ( $u_obj->checkPassword( $password, false, false ) == true ) {
 | 
						|
							if ( $u_obj->getEnableLogin() == false ) {
 | 
						|
								$error_message = TTi18n::gettext( 'Sorry, your login is currently disabled, please contact your supervisor or manager to request access.' );
 | 
						|
							} else {
 | 
						|
								if ( $u_obj->isFirstLogin() == true && $u_obj->isCompromisedPassword() == true ) {
 | 
						|
									$error_message = TTi18n::gettext( 'Welcome to %1, since this is your first time logging in, we ask that you change your password to something more secure', [ APPLICATION_NAME ] );
 | 
						|
									$error_column = 'password';
 | 
						|
								} else if ( $u_obj->isCompromisedPassword() == true && $u_obj->isFirstLogin() == false ) {
 | 
						|
									$error_message = TTi18n::gettext( 'Due to your company\'s password policy, your password must be changed immediately' );
 | 
						|
									$error_column = 'password';
 | 
						|
								} else if ( $u_obj->isPasswordPolicyEnabled() == true ) {
 | 
						|
									if ( $u_obj->checkPasswordAge() == false ) {
 | 
						|
										//Password policy is enabled, confirm users password has not exceeded maximum age.
 | 
						|
										//Make sure we confirm that the password is in fact correct, but just expired.
 | 
						|
										$error_message = TTi18n::gettext( 'Your password has exceeded its maximum age specified by your company\'s password policy and must be changed immediately' );
 | 
						|
										$error_column = 'password';
 | 
						|
									}
 | 
						|
								} else {
 | 
						|
									Debug::text( '  Password matches, but other criteria denied...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
								}
 | 
						|
							}
 | 
						|
						} else {
 | 
						|
							if ( $u_obj->checkLoginPermissions() == false ) {
 | 
						|
								$error_message = TTi18n::gettext( 'Sorry, you don\'t have permission to login, please contact your supervisor or manager to request access.' );
 | 
						|
							}
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						Debug::text( '  User Name: ' . $user_name .' record count: '. $ulf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					}
 | 
						|
					unset( $ulf, $u_obj );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				Debug::text( '  User Name: ' . $user_name .' linked to company, record count: '. $clf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			}
 | 
						|
 | 
						|
			$validator_obj->isTrue( $error_column, false, $error_message );
 | 
						|
 | 
						|
			$validator[0] = $validator_obj->getErrorsArray();
 | 
						|
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $validator, $validator_stats );
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Register permanent API key Session ID to be used for all subsequent API calls without needing a username/password.
 | 
						|
	 * @param string $user_name
 | 
						|
	 * @param string $password
 | 
						|
	 * @return bool|string
 | 
						|
	 */
 | 
						|
	function registerAPIKey( $user_name, $password, $end_point = null ) {
 | 
						|
		$session_type = $this->stripReturnHandler( $this->getSessionTypeForLogin( $user_name ) );
 | 
						|
 | 
						|
		if ( $session_type['mfa_type_id'] > 0 ) {
 | 
						|
			//This function is not available for MFA enabled users.
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		//Always require UserName/Password when registering an API key, as from a security stand-point this is similar to changing passwords.
 | 
						|
		//  If its a master administrator, only need to register an API key for the master administrator employee, then they can switchUser() to any other user as needed with that same key.
 | 
						|
		if ( $user_name != '' && $password != '' ) {
 | 
						|
			$authentication = new Authentication();
 | 
						|
			$api_key = $authentication->registerAPIKey( $user_name, $password, $end_point );
 | 
						|
			Debug::text( 'User Name: ' . $user_name .' API Key: '. $api_key .' End Point: '. $end_point, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			return $api_key; //Don't wrap in return handler.
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param $end_point
 | 
						|
	 * @return array|bool
 | 
						|
	 */
 | 
						|
	function registerAPIKeyForCurrentUser( $end_point = 'json/api' ) {
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( $this->isLoggedIn() == false ) {
 | 
						|
			return $this->returnHandler( false );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $authentication->isSessionReauthenticated() === false ) {
 | 
						|
			return $this->getPermissionObject()->ReauthenticationRequired( $this->getCurrentUserObject() );
 | 
						|
		}
 | 
						|
 | 
						|
		$api_key = $authentication->registerAPIKeyForCurrentUser( $end_point );
 | 
						|
 | 
						|
		//One time auth is used to verify single actions that require re-authentication and needs to be removed after used.
 | 
						|
		$authentication->reauthenticationActionCompleted();
 | 
						|
 | 
						|
		return $this->returnHandler( $api_key );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param string $user_id                   UUID
 | 
						|
	 * @param string $invoice_invoice_client_id UUID
 | 
						|
	 * @param string $ip_address
 | 
						|
	 * @param string $user_agent
 | 
						|
	 * @param string $client_id                 UUID
 | 
						|
	 * @param string $end_point_id
 | 
						|
	 * @param null $type_id
 | 
						|
	 * @return array|bool
 | 
						|
	 * @throws DBError
 | 
						|
	 * @throws ReflectionException
 | 
						|
	 */
 | 
						|
	function newSession( $user_id, $invoice_invoice_client_id = null, $ip_address = null, $user_agent = null, $client_id = null, $end_point_id = null, $type_id = null ) {
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( is_object( $authentication ) && $authentication->getSessionID() != '' ) {
 | 
						|
			Debug::text( 'Session ID: ' . $authentication->getSessionID() .' Encrypted: '. $authentication->encryptSessionID( $authentication->getSessionID() ) .' Type ID: '. $authentication->getType(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			if ( $this->getPermissionObject()->checkAuthenticationType( 700 ) == false ) { //700=API Key
 | 
						|
				return $this->getPermissionObject()->AuthenticationTypeDenied();
 | 
						|
			}
 | 
						|
 | 
						|
			if ( $this->getPermissionObject()->Check( 'company', 'view' ) && $this->getPermissionObject()->Check( 'company', 'login_other_user' ) ) {
 | 
						|
				if ( TTUUID::isUUID( $user_id ) == false ) { //If username is used, lookup user_id
 | 
						|
					Debug::Text( 'Lookup User ID by UserName: ' . $user_id, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
					$ulf->getByUserName( trim( $user_id ) );
 | 
						|
					if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
						$user_id = $ulf->getCurrent()->getID();
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
				$ulf->getByIdAndStatus( TTUUID::castUUID( $user_id ), 10 );  //Can only switch to Active employees
 | 
						|
				if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
					$new_session_user_obj = $ulf->getCurrent();
 | 
						|
 | 
						|
					if ( $client_id == '' ) {
 | 
						|
						$client_id = 'browser-timetrex';
 | 
						|
					}
 | 
						|
 | 
						|
					if ( $end_point_id == '' ) {
 | 
						|
						$end_point_id = 'json/api';
 | 
						|
					}
 | 
						|
 | 
						|
					if ( $user_agent == '' ) {
 | 
						|
						$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null;
 | 
						|
					}
 | 
						|
 | 
						|
					Debug::Text( 'Login as different user: ' . $user_id . ' IP Address: ' . $ip_address . ' Client ID: ' . $client_id . ' End Point ID: ' . $end_point_id . ' Type ID: '. $type_id .' User Agent: ' . $user_agent, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$new_session_id = $authentication->newSession( $user_id, $ip_address, $user_agent, $client_id, $end_point_id, $type_id );
 | 
						|
 | 
						|
					$retarr = [
 | 
						|
							'session_id'      => $new_session_id,
 | 
						|
							'url'             => Misc::getHostName( false ) . Environment::getBaseURL(), //Don't include the port in the hostname, otherwise it can cause problems when forcing port 443 but not using 'https'.
 | 
						|
							'cookie_base_url' => Environment::getCookieBaseURL(),
 | 
						|
					];
 | 
						|
 | 
						|
					//Add entry in source *AND* destination user log describing who logged in.
 | 
						|
					//Source user log, showing that the source user logged in as someone else.
 | 
						|
					TTLog::addEntry( $this->getCurrentUserObject()->getId(), 100, TTi18n::getText( 'Override Login' ) . ': ' . TTi18n::getText( 'SourceIP' ) . ': ' . $authentication->getIPAddress() . ' ' . TTi18n::getText( 'SessionID' ) . ': ' . $authentication->getSecureSessionID() . ' ' . TTi18n::getText( 'To Employee' ) . ': ' . $new_session_user_obj->getFullName() . ' (' . $user_id . ')', $this->getCurrentUserObject()->getId(), 'authentication' );
 | 
						|
 | 
						|
					//Destination user log, showing the destination user was logged in *by* someone else.
 | 
						|
					TTLog::addEntry( $user_id, 100, TTi18n::getText( 'Override Login' ) . ': ' . TTi18n::getText( 'SourceIP' ) . ': ' . $authentication->getIPAddress() . ' ' . TTi18n::getText( 'SessionID' ) . ': ' . $authentication->getSecureSessionID() . ' ' . TTi18n::getText( 'By Employee' ) . ': ' . $this->getCurrentUserObject()->getFullName() . ' (' . $user_id . ')', $user_id, 'authentication' );
 | 
						|
 | 
						|
					return $this->returnHandler( $retarr );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				Debug::text( '  ERROR: Permission check failed for logging in as another user...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Accepts user_id or user_name.
 | 
						|
	 * @param string $user_id UUID
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function switchUser( $user_id ) {
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( is_object( $authentication ) && $authentication->getSessionID() != '' ) {
 | 
						|
			Debug::text( 'Session ID: ' . $authentication->getSessionID(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			if ( $this->getPermissionObject()->checkAuthenticationType( 700 ) == false ) { //700=API Key
 | 
						|
				return $this->getPermissionObject()->AuthenticationTypeDenied();
 | 
						|
			}
 | 
						|
 | 
						|
			if ( $this->getPermissionObject()->Check( 'company', 'view' ) && $this->getPermissionObject()->Check( 'company', 'login_other_user' ) ) {
 | 
						|
				if ( TTUUID::isUUID( $user_id ) == false ) { //If username is used, lookup user_id
 | 
						|
					Debug::Text( 'Lookup User ID by UserName: ' . $user_id, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
					$ulf->getByUserName( trim( $user_id ) );
 | 
						|
					if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
						$user_id = $ulf->getCurrent()->getID();
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
				$ulf->getByIdAndStatus( TTUUID::castUUID( $user_id ), 10 );  //Can only switch to Active employees
 | 
						|
				if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
					Debug::Text( 'Login as different user: ' . $user_id, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$authentication->changeObject( $user_id );
 | 
						|
 | 
						|
					//Add entry in source *AND* destination user log describing who logged in.
 | 
						|
					//Source user log, showing that the source user logged in as someone else.
 | 
						|
					TTLog::addEntry( $this->getCurrentUserObject()->getId(), 100, TTi18n::getText( 'Override Login' ) . ': ' . TTi18n::getText( 'SourceIP' ) . ': ' . $authentication->getIPAddress() . ' ' . TTi18n::getText( 'SessionID' ) . ': ' . $authentication->getSecureSessionID() . ' ' . TTi18n::getText( 'To Employee' ) . ': ' . $authentication->getObject()->getFullName() . ' (' . $user_id . ')', $this->getCurrentUserObject()->getId(), 'authentication' );
 | 
						|
 | 
						|
					//Destination user log, showing the destination user was logged in *by* someone else.
 | 
						|
					TTLog::addEntry( $user_id, 100, TTi18n::getText( 'Override Login' ) . ': ' . TTi18n::getText( 'SourceIP' ) . ': ' . $authentication->getIPAddress() . ' ' . TTi18n::getText( 'SessionID' ) . ': ' . $authentication->getSecureSessionID() . ' ' . TTi18n::getText( 'By Employee' ) . ': ' . $this->getCurrentUserObject()->getFullName() . ' (' . $user_id . ')', $user_id, 'authentication' );
 | 
						|
 | 
						|
					return true;
 | 
						|
				} else {
 | 
						|
					Debug::Text( 'User is likely not active: ' . $user_id, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				Debug::text( '  ERROR: Permission check failed for switching users...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function Logout() {
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( is_object( $authentication ) && $authentication->getSessionID() != '' ) {
 | 
						|
			Debug::text( 'Logging out session ID: ' . $authentication->getSessionID(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			return $authentication->Logout();
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return int
 | 
						|
	 */
 | 
						|
	function getSessionIdle() {
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( !is_object( $authentication ) ) {
 | 
						|
			$authentication = new Authentication();
 | 
						|
		}
 | 
						|
 | 
						|
		return $authentication->getIdleTimeout();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param bool $touch_updated_date
 | 
						|
	 * @param string|array $type
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function isLoggedIn( $touch_updated_date = true, $type = [ 'USER_NAME', 'USER_NAME_MULTI_FACTOR' ] ) {
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( is_array( $type ) == false ) {
 | 
						|
			$type = [ $type ];
 | 
						|
		}
 | 
						|
 | 
						|
		$session_id = getSessionID( $type[0] );
 | 
						|
 | 
						|
		if ( $session_id != '' ) {
 | 
						|
			$authentication = new Authentication();
 | 
						|
			Debug::text( 'Session ID: ' . $session_id . ' Source IP: ' . Misc::getRemoteIPAddress() . ' Touch Updated Date: ' . (int)$touch_updated_date, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			if ( $authentication->Check( $session_id, $type, $touch_updated_date ) === true ) {
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function getCurrentUserName() {
 | 
						|
		if ( is_object( $this->getCurrentUserObject() ) ) {
 | 
						|
			return $this->returnHandler( $this->getCurrentUserObject()->getUserName() );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function getCurrentUser() {
 | 
						|
		if ( is_object( $this->getCurrentUserObject() ) ) {
 | 
						|
			return $this->returnHandler( $this->getCurrentUserObject()->getObjectAsArray( [ 'id' => true, 'company_id' => true, 'currency_id' => true, 'permission_control_id' => true, 'pay_period_schedule_id' => true, 'policy_group_id' => true, 'default_branch_id' => true, 'default_department_id' => true, 'default_job_id' => true, 'default_job_item_id' => true, 'employee_number' => true, 'user_name' => true, 'phone_id' => true, 'first_name' => true, 'middle_name' => true, 'last_name' => true, 'full_name' => true, 'city' => true, 'province' => true, 'country' => true, 'longitude' => true, 'latitude' => true, 'work_phone' => true, 'home_phone' => true, 'work_email' => true, 'home_email' => true, 'feedback_rating' => true, 'prompt_for_feedback' => true, 'last_login_date' => true, 'created_date' => true, 'is_owner' => true, 'is_child' => true ] ) );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function getCurrentCompany() {
 | 
						|
		if ( is_object( $this->getCurrentCompanyObject() ) ) {
 | 
						|
			return $this->returnHandler( $this->getCurrentCompanyObject()->getObjectAsArray( [ 'id' => true, 'product_edition_id' => true, 'name' => true, 'short_name' => true, 'industry' => true, 'city' => true, 'province' => true, 'country' => true, 'work_phone' => true, 'application_build' => true, 'is_setup_complete' => true, 'total_active_days' => true, 'created_date' => true, 'latitude' => true, 'longitude' => true ] ) );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function getCustomFieldData() {
 | 
						|
		$cflf = TTNew( 'CustomFieldListFactory' ); /** @var CustomFieldListFactory $cflf */
 | 
						|
 | 
						|
		$retarr = [
 | 
						|
				'parent_tables'         => $cflf->getUniqueParentTableByCompanyId( $this->getCurrentCompanyObject()->getId() ),
 | 
						|
				'conversion_field_types' => $cflf->getOptions( 'conversion_field_types' ),
 | 
						|
		];
 | 
						|
 | 
						|
		return $this->returnHandler( $retarr );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function getCurrentUserPreference() {
 | 
						|
		if ( is_object( $this->getCurrentUserObject() ) && is_object( $this->getCurrentUserObject()->getUserPreferenceObject() ) ) {
 | 
						|
			return $this->returnHandler( $this->getCurrentUserObject()->getUserPreferenceObject()->getObjectAsArray() );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Functions that can be called before the API client is logged in.
 | 
						|
	 * Mainly so the proper loading/login page can be displayed.
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function getProduction() {
 | 
						|
		return PRODUCTION;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function getApplicationName() {
 | 
						|
		return APPLICATION_NAME;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function getApplicationVersion() {
 | 
						|
		return APPLICATION_VERSION;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return int
 | 
						|
	 */
 | 
						|
	function getApplicationVersionDate() {
 | 
						|
		return APPLICATION_VERSION_DATE;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function getApplicationBuild() {
 | 
						|
		return APPLICATION_BUILD;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function getOrganizationName() {
 | 
						|
		return ORGANIZATION_NAME;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function getOrganizationURL() {
 | 
						|
		return ORGANIZATION_URL;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function isApplicationBranded() {
 | 
						|
		global $config_vars;
 | 
						|
 | 
						|
		if ( isset( $config_vars['branding']['application_name'] ) ) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function isPoweredByLogoEnabled() {
 | 
						|
		global $config_vars;
 | 
						|
 | 
						|
		if ( isset( $config_vars['branding']['disable_powered_by_logo'] ) && $config_vars['branding']['disable_powered_by_logo'] == true ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function isAnalyticsEnabled() {
 | 
						|
		global $config_vars;
 | 
						|
 | 
						|
		if ( isset( $config_vars['other']['disable_google_analytics'] ) && $config_vars['other']['disable_google_analytics'] == true ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function getAnalyticsTrackingCode() {
 | 
						|
		global $config_vars;
 | 
						|
 | 
						|
		if ( isset( $config_vars['other']['analytics_tracking_code'] ) && $config_vars['other']['analytics_tracking_code'] != '' ) {
 | 
						|
			return $config_vars['other']['analytics_tracking_code'];
 | 
						|
		}
 | 
						|
 | 
						|
		return 'G-4MSFN7PM0H'; //GA4 - OnSite
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param bool $name
 | 
						|
	 * @return int|string
 | 
						|
	 */
 | 
						|
	function getTTProductEdition( $name = false ) {
 | 
						|
		if ( $name == true ) {
 | 
						|
			$edition = getTTProductEditionName();
 | 
						|
		} else {
 | 
						|
			$edition = getTTProductEdition();
 | 
						|
		}
 | 
						|
 | 
						|
		Debug::text( 'Edition: ' . $edition, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		return $edition;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function getDeploymentOnDemand() {
 | 
						|
		return DEPLOYMENT_ON_DEMAND;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function getRegistrationKey() {
 | 
						|
		return SystemSettingFactory::getSystemSettingValueByKey( 'registration_key' );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param null $language
 | 
						|
	 * @param null $country
 | 
						|
	 * @return null
 | 
						|
	 */
 | 
						|
	function getLocale( $language = null, $country = null ) {
 | 
						|
		$language = Misc::trimSortPrefix( $language );
 | 
						|
		if ( $language == '' && is_object( $this->getCurrentUserObject() ) && is_object( $this->getCurrentUserObject()->getUserPreferenceObject() ) ) {
 | 
						|
			$language = $this->getCurrentUserObject()->getUserPreferenceObject()->getLanguage();
 | 
						|
		}
 | 
						|
		if ( $country == '' && is_object( $this->getCurrentUserObject() ) ) {
 | 
						|
			$country = $this->getCurrentUserObject()->getCountry();
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $language != '' ) {
 | 
						|
			TTi18n::setLanguage( $language );
 | 
						|
		}
 | 
						|
		if ( $country != '' ) {
 | 
						|
			TTi18n::setCountry( $country );
 | 
						|
		}
 | 
						|
		TTi18n::setLocale(); //Sets master locale
 | 
						|
 | 
						|
		//$retval = str_replace('.UTF-8', '', TTi18n::getLocale() );
 | 
						|
		$retval = TTi18n::getNormalizedLocale();
 | 
						|
 | 
						|
		Debug::text( 'Locale: ' . $retval . ' Language: ' . $language, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		return $retval;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return int|mixed
 | 
						|
	 */
 | 
						|
	function getSystemLoad() {
 | 
						|
		return Misc::getSystemLoad();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return mixed
 | 
						|
	 */
 | 
						|
	function getHTTPHost() {
 | 
						|
		return $_SERVER['HTTP_HOST'];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function getCompanyName() {
 | 
						|
		//Get primary company data needs to be used when user isn't logged in as well.
 | 
						|
		$clf = TTnew( 'CompanyListFactory' ); /** @var CompanyListFactory $clf */
 | 
						|
		$clf->getByID( PRIMARY_COMPANY_ID );
 | 
						|
		Debug::text( 'Primary Company ID: ' . PRIMARY_COMPANY_ID, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		if ( $clf->getRecordCount() == 1 ) {
 | 
						|
			return $clf->getCurrent()->getName();
 | 
						|
		}
 | 
						|
 | 
						|
		Debug::text( '  ERROR: Primary Company does not exist!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Returns all login data required in a single call for optimization purposes.
 | 
						|
	 * @param null $api
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function getPreLoginData( $api = null, $authentication_type = [ 'USER_NAME', 'USER_NAME_MULTI_FACTOR'] ) {
 | 
						|
		global $config_vars;
 | 
						|
 | 
						|
		//Get browser information from user agent. Used below to get vendor and if browser is mobil or not.
 | 
						|
		$browser = new Browser( ( ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) ? $_SERVER['HTTP_USER_AGENT'] : null ) );
 | 
						|
 | 
						|
		//Basic settings that *do not* require a DB connection.
 | 
						|
		$retarr = [
 | 
						|
				'primary_company_id'                  => PRIMARY_COMPANY_ID, //Needed for some branded checks.
 | 
						|
				'primary_company_name'                => null, //Requires DB connection.
 | 
						|
				'base_url'                            => Environment::getBaseURL(),
 | 
						|
				'cookie_base_url'                     => Environment::getCookieBaseURL( 'json' ),
 | 
						|
				'api_url'                             => Environment::getAPIURL( $api ),
 | 
						|
				'api_base_url'                        => Environment::getAPIBaseURL( $api ),
 | 
						|
				'api_json_url'                        => Environment::getAPIURL( 'json' ),
 | 
						|
				'images_url'                          => Environment::getImagesURL(),
 | 
						|
				'application_name'                    => $this->getApplicationName(),
 | 
						|
				'organization_name'                   => $this->getOrganizationName(),
 | 
						|
				'organization_url'                    => $this->getOrganizationURL(),
 | 
						|
				'copyright_notice'                    => COPYRIGHT_NOTICE,
 | 
						|
				'product_edition'                     => $this->getTTProductEdition( false ),
 | 
						|
				'product_edition_name'                => $this->getTTProductEdition( true ),
 | 
						|
				'deployment_on_demand'                => $this->getDeploymentOnDemand(),
 | 
						|
				'web_session_expire'                  => ( isset( $config_vars['other']['web_session_expire'] ) && $config_vars['other']['web_session_expire'] != '' ) ? (bool)$config_vars['other']['web_session_expire'] : false, //If TRUE then session expires when browser closes.
 | 
						|
				'analytics_enabled'                   => $this->isAnalyticsEnabled(),
 | 
						|
				'analytics_tracking_code'             => $this->getAnalyticsTrackingCode(),
 | 
						|
				'registration_key'                    => null, //Requires DB connection.
 | 
						|
				'http_host'                           => $this->getHTTPHost(),
 | 
						|
				'is_ssl'                              => Misc::isSSL(),
 | 
						|
				'production'                          => $this->getProduction(),
 | 
						|
				'demo_mode'                           => DEMO_MODE,
 | 
						|
				'application_version'                 => $this->getApplicationVersion(),
 | 
						|
				'application_version_date'            => $this->getApplicationVersionDate(),
 | 
						|
				'application_build'                   => $this->getApplicationBuild(),
 | 
						|
				'installer_enabled'                   => ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] != '' ) ? (bool)$config_vars['other']['installer_enabled'] : false,
 | 
						|
				'is_logged_in'                        => false, //Requires DB connection.
 | 
						|
				'session_idle_timeout'                => $this->getSessionIdle(),
 | 
						|
				'footer_left_html'                    => ( isset( $config_vars['other']['footer_left_html'] ) && $config_vars['other']['footer_left_html'] != '' ) ? $config_vars['other']['footer_left_html'] : false,
 | 
						|
				'footer_right_html'                   => ( isset( $config_vars['other']['footer_right_html'] ) && $config_vars['other']['footer_right_html'] != '' ) ? $config_vars['other']['footer_right_html'] : false,
 | 
						|
				'disable_feedback'                    => ( isset( $config_vars['other']['disable_feedback'] ) && $config_vars['other']['disable_feedback'] != '' ) ? (bool)$config_vars['other']['disable_feedback'] : false,
 | 
						|
				'support_email'                       => ( isset( $config_vars['other']['support_email'] ) ) ? $config_vars['other']['support_email'] : 'support@timetrex.com', //Allow this to be defined as empty to disable the Email Support icon.
 | 
						|
				'language_options'                    => Misc::addSortPrefix( TTi18n::getLanguageArray() ),
 | 
						|
				//Make sure locale is set properly before this function is called, either in api.php or APIGlobal.js.php for example.
 | 
						|
				'enable_default_language_translation' => ( isset( $config_vars['other']['enable_default_language_translation'] ) ) ? $config_vars['other']['enable_default_language_translation'] : false,
 | 
						|
				'language'                            => TTi18n::getLanguage(),
 | 
						|
				'locale'                              => TTi18n::getNormalizedLocale(), //Needed for HTML5 interface to load proper translation file.
 | 
						|
 | 
						|
				'map_provider'    => isset( $config_vars['map']['provider'] ) ? $config_vars['map']['provider'] : 'mapbox',
 | 
						|
				'map_api_key'     => ( isset( $config_vars['map']['api_key'] ) && $config_vars['map']['api_key'] != '' ) ? $config_vars['map']['api_key'] : 'pk.eyJ1IjoidGltZXRyZXgiLCJhIjoiY2t1OHBxejEyNXI3ajJwcGk5d3cxNGJkeSJ9.4O1x-ULp4DuSRbeXJTIL3w', //On-Site.
 | 
						|
																																																																																																																								/* @formatter:off */ 'product_edition_match' => (new Install())->checkProductEditionMatch(), /* @formatter:on */
 | 
						|
				//Registration key for the map servers must be added in JS because of the url formats
 | 
						|
				'map_tile_url'    => isset( $config_vars['map']['tile_url'] ) ? rtrim( $config_vars['map']['tile_url'], '/' ) : 'https://map-tiles.timetrex.com',
 | 
						|
				'map_routing_url' => isset( $config_vars['map']['routing_url'] ) ? rtrim( $config_vars['map']['routing_url'], '/' ) : 'https://map-routing.timetrex.com',
 | 
						|
				'map_geocode_url' => isset( $config_vars['map']['geocode_url'] ) ? rtrim( $config_vars['map']['geocode_url'], '/' ) : 'https://map-geocode.timetrex.com',
 | 
						|
 | 
						|
				'sandbox_url' => isset( $config_vars['other']['sandbox_url'] ) ? $config_vars['other']['sandbox_url'] : false,
 | 
						|
				'sandbox'     => isset( $config_vars['other']['sandbox'] ) ? $config_vars['other']['sandbox'] : false,
 | 
						|
				'uuid_seed'   => TTUUID::getSeed( true ),
 | 
						|
 | 
						|
				'user_agent_data' 	=> [ 'browser' => $browser->getBrowser(), 'is_mobile' => $browser->isMobile() ],
 | 
						|
				'feature_flags' 	=> Misc::parseFeatureFlags(),
 | 
						|
		];
 | 
						|
 | 
						|
		if ( ( isset( $_GET['disable_db'] ) && $_GET['disable_db'] == 1 )
 | 
						|
				|| ( isset( $config_vars['other']['installer_enabled'] ) && $config_vars['other']['installer_enabled'] == 1 )
 | 
						|
				|| ( isset( $config_vars['other']['down_for_maintenance'] ) && $config_vars['other']['down_for_maintenance'] == true ) ) {
 | 
						|
			Debug::text( 'WARNING: Installer/Down For Maintenance is enabled... Normal logins are disabled!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		} else {
 | 
						|
			//Only data that requires a DB connection to obtain here.
 | 
						|
			$retarr['company_name'] = $this->getCompanyName();
 | 
						|
			if ( $retarr['company_name'] == '' ) {
 | 
						|
				$retarr['company_name'] = 'N/A';
 | 
						|
			}
 | 
						|
 | 
						|
			$retarr['registration_key'] = $this->getRegistrationKey();
 | 
						|
			$retarr['is_logged_in'] = $this->isLoggedIn( true, $authentication_type );
 | 
						|
		}
 | 
						|
 | 
						|
		return $retarr;
 | 
						|
	}
 | 
						|
	/**
 | 
						|
	 * Returns all post login data required in a single call for optimization purposes.
 | 
						|
	 * @param array $data
 | 
						|
	 * @return array | bool
 | 
						|
	 */
 | 
						|
	function getPostLoginData( $data = [] ) {
 | 
						|
 | 
						|
		if ( !is_object( $this->getCurrentUserObject() ) ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		$retarr = [];
 | 
						|
 | 
						|
		$retarr['user_data'] = $this->stripReturnHandler( $this->getCurrentUser() );
 | 
						|
		$retarr['user_preference'] = $this->stripReturnHandler( $this->getCurrentUserPreference() );
 | 
						|
		$retarr['company_data'] = $this->stripReturnHandler( $this->getCurrentCompany() );
 | 
						|
		$retarr['locale'] = $this->getLocale( $data['selected_language'] ?? null );
 | 
						|
 | 
						|
		$retarr['custom_field_data'] = $this->stripReturnHandler( $this->getCustomFieldData() );
 | 
						|
 | 
						|
		$permission = new Permission();
 | 
						|
		$retarr['permissions'] = $permission->getPermissions( $this->getCurrentUserObject()->getId(), $this->getCurrentCompanyObject()->getId() );
 | 
						|
 | 
						|
		$clf = TTnew( 'CurrencyListFactory' );
 | 
						|
		$clf->getByCompanyIdAndUserId( $this->getCurrentCompanyObject()->getId(), $this->getCurrentUserObject()->getId() );
 | 
						|
		if ( $clf->getRecordCount() > 0 ) {
 | 
						|
			$c_obj = $clf->getCurrent();
 | 
						|
			$retarr['currency_symbol'] = $c_obj->getSymbol();
 | 
						|
		} else {
 | 
						|
			$retarr['currency_symbol'] = '$';
 | 
						|
		}
 | 
						|
 | 
						|
		$ulf = TTNew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
		$retarr['unique_country'] = $ulf->getUniqueCountryByCompanyId( $this->getCurrentCompanyObject()->getId() );
 | 
						|
 | 
						|
		$api_user_preference = new APIUserPreference();
 | 
						|
		$retarr['moment_date_format'] = $api_user_preference->getOptions( 'moment_date_format' );
 | 
						|
		$retarr['moment_time_format'] = $api_user_preference->getOptions( 'moment_time_format' );
 | 
						|
 | 
						|
		if ( !$retarr['user_preference'] || !isset( $retarr['user_preference']['date_format'] ) ) {
 | 
						|
			//Get default user preferences if user does not have any set.
 | 
						|
			$retarr['user_preference'] = $this->stripReturnHandler( $api_user_preference->getUserPreferenceDefaultData() );
 | 
						|
		}
 | 
						|
 | 
						|
		$retarr['feature_flags'] = Misc::parseFeatureFlags();
 | 
						|
 | 
						|
		return $retarr;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Function that HTML5 interface can call when an irrecoverable error or uncaught exception is triggered.
 | 
						|
	 * @param null $data
 | 
						|
	 * @param null $screenshot
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	function sendErrorReport( $data = null, $screenshot = null ) {
 | 
						|
		$rl = TTNew( 'RateLimit' ); /** @var RateLimit $rl */
 | 
						|
		$rl->setID( 'error_report_' . Misc::getRemoteIPAddress() );
 | 
						|
		$rl->setAllowedCalls( 20 );
 | 
						|
		$rl->setTimeFrame( 900 ); //15 minutes
 | 
						|
		if ( $rl->check() == false ) {
 | 
						|
			Debug::Text( 'Excessive error reports... Preventing error reports from: ' . Misc::getRemoteIPAddress() . ' for up to 15 minutes...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
			return APPLICATION_BUILD;
 | 
						|
		}
 | 
						|
 | 
						|
		$attachments = null;
 | 
						|
		if ( $screenshot != '' ) {
 | 
						|
			$attachments[] = [ 'file_name' => 'screenshot.png', 'mime_type' => 'image/png', 'data' => base64_decode( $screenshot ) ];
 | 
						|
		}
 | 
						|
 | 
						|
		$subject = 'HTML5 Error Report'; //Don't translate this, as it breaks filters.
 | 
						|
 | 
						|
		$data = 'IP Address: ' . Misc::getRemoteIPAddress() . "\nServer Version: " . APPLICATION_BUILD . "\nUser Agent: " . ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'N/A' ) . "\n\n" . $data;
 | 
						|
 | 
						|
		Misc::sendSystemMail( $subject, $data, $attachments ); //Do not send if PRODUCTION=FALSE.
 | 
						|
 | 
						|
		//return APPLICATION_BUILD so JS can check if its correct and notify the user to refresh/clear cache.
 | 
						|
		return APPLICATION_BUILD;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Allows user who isn't logged in to change their password.
 | 
						|
	 * @param string $user_name
 | 
						|
	 * @param string $current_password
 | 
						|
	 * @param string $new_password
 | 
						|
	 * @param string $new_password2
 | 
						|
	 * @return array|bool
 | 
						|
	 * @internal param string $type
 | 
						|
	 */
 | 
						|
	function changePassword( $user_name, $current_password = null, $new_password = null, $new_password2 = null ) {
 | 
						|
		$rl = TTNew( 'RateLimit' ); /** @var RateLimit $rl */
 | 
						|
		$rl->setID( 'authentication_' . Misc::getRemoteIPAddress() );
 | 
						|
		$rl->setAllowedCalls( 20 );
 | 
						|
		$rl->setTimeFrame( 900 ); //15 minutes
 | 
						|
 | 
						|
		if ( $rl->check() == false ) {
 | 
						|
			Debug::Text( 'Excessive failed password attempts... Preventing password change from: ' . Misc::getRemoteIPAddress() . ' for up to 15 minutes...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			sleep( 5 ); //Excessive password attempts, sleep longer.
 | 
						|
			$u_obj = TTnew( 'UserListFactory' ); /** @var UserListFactory $u_obj */
 | 
						|
			$u_obj->Validator->isTrue( 'current_password', false, TTi18n::gettext( 'Current User Name or Password is incorrect' ) );
 | 
						|
 | 
						|
			return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $u_obj->Validator->getErrorsArray(), [ 'total_records' => 1, 'valid_records' => 0 ] );
 | 
						|
		}
 | 
						|
 | 
						|
		$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
		$ulf->getByUserName( $user_name );
 | 
						|
		if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
			$u_obj = $ulf->getCurrent();
 | 
						|
			if ( is_object( $u_obj->getCompanyObject() ) && $u_obj->getCompanyObject()->getStatus() == 10 ) {
 | 
						|
				Debug::text( 'Attempting to change password for: ' . $user_name, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
				$u_obj->setIsRequiredCurrentPassword( true );
 | 
						|
				$u_obj->setCurrentPassword( $current_password );
 | 
						|
 | 
						|
				if ( $current_password != '' ) {
 | 
						|
					if ( $u_obj->checkPassword( $current_password, false ) !== true ) { //Disable password policy checking on current password.
 | 
						|
						Debug::text( 'Password check failed! Attempt: ' . $rl->getAttempts(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
						sleep( ( $rl->getAttempts() * 0.5 ) ); //If password is incorrect, sleep for some time to slow down brute force attacks.
 | 
						|
						//setCurrentPassword() above handles this validation error message now.
 | 
						|
//						$u_obj->Validator->isTrue( 'current_password',
 | 
						|
//												   FALSE,
 | 
						|
//												   TTi18n::gettext( 'Current User Name or Password is incorrect' ) );
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					Debug::Text( 'Current password not specified', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$u_obj->Validator->isTrue( 'current_password',
 | 
						|
											   false,
 | 
						|
											   TTi18n::gettext( 'Current User Name or Password is incorrect' ) );
 | 
						|
				}
 | 
						|
 | 
						|
				if ( $current_password == $new_password ) {
 | 
						|
					$u_obj->Validator->isTrue( 'password',
 | 
						|
											   false,
 | 
						|
											   TTi18n::gettext( 'New password must be different than current password' ) );
 | 
						|
				} else {
 | 
						|
					if ( $new_password != '' || $new_password2 != '' ) {
 | 
						|
						if ( $new_password == $new_password2 ) {
 | 
						|
							$u_obj->setPassword( $new_password );
 | 
						|
						} else {
 | 
						|
							$u_obj->Validator->isTrue( 'password',
 | 
						|
													   false,
 | 
						|
													   TTi18n::gettext( 'Passwords don\'t match' ) );
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						$u_obj->Validator->isTrue( 'password',
 | 
						|
												   false,
 | 
						|
												   TTi18n::gettext( 'Passwords don\'t match' ) );
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				//This should force the updated_by field to match the user changing their password,
 | 
						|
				//  so we know not to ask the user to change their password again, since they were the last ones to do so.
 | 
						|
				//$current_user must be set above $u_obj->isValid() so it can properly validate things like hierarchy and such in UserFactory.
 | 
						|
				global $current_user;
 | 
						|
				$current_user = $u_obj;
 | 
						|
 | 
						|
				if ( $u_obj->isValid() ) {
 | 
						|
					if ( DEMO_MODE == true ) {
 | 
						|
						//Return TRUE even in demo mode, but nothing happens.
 | 
						|
						return $this->returnHandler( true );
 | 
						|
					} else {
 | 
						|
						TTLog::addEntry( $u_obj->getID(), 20, TTi18n::getText( 'Password - Web (Password Policy)' ), null, $u_obj->getTable() );
 | 
						|
						$rl->delete(); //Clear failed password rate limit upon successful login.
 | 
						|
 | 
						|
						$u_obj->setPasswordUpdatedDate( time() ); //Since the user isn't logged in, we have to manually force the PasswordUpdatedDate here to ensure its no longer -1 (compromised)
 | 
						|
						$retval = $u_obj->Save( false ); //UserID is needed below.
 | 
						|
 | 
						|
						//Logout all other sessions for this user.
 | 
						|
						$authentication = TTNew( 'Authentication' ); /** @var Authentication $authentication */
 | 
						|
						$authentication->logoutUser( $u_obj->getID() );
 | 
						|
 | 
						|
						$current_user = null; //unset( $current_user ); -- unset() doesn't work on global variables.
 | 
						|
 | 
						|
						return $this->returnHandler( $retval ); //Single valid record
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				$u_obj = TTnew( 'UserListFactory' ); /** @var UserListFactory $u_obj */
 | 
						|
				$u_obj->Validator->isTrue( 'current_password', false, TTi18n::gettext( 'Sorry, your company\'s account is not currently ACTIVE, please contact customer support' ) );
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			//Issue #2225 - Be sure to return the same error message even if username is not valid to avoid user enumeration attacks.
 | 
						|
			$u_obj = TTnew( 'UserListFactory' ); /** @var UserListFactory $u_obj */
 | 
						|
			$u_obj->Validator->isTrue( 'current_password', false, TTi18n::gettext( 'Current User Name or Password is incorrect' ) );
 | 
						|
		}
 | 
						|
 | 
						|
		sleep( ( $rl->getAttempts() * 0.5 ) ); //If password is incorrect, sleep for some time to slow down brute force attacks.
 | 
						|
		Debug::Text( 'Failed username/password... Attempt: ' . $rl->getAttempts() . ' Sleeping...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
		return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), $u_obj->Validator->getErrorsArray(), [ 'total_records' => 1, 'valid_records' => 0 ] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param $email
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function resetPassword( $email ) {
 | 
						|
		//Debug::setVerbosity( 11 );
 | 
						|
		$rl = TTNew( 'RateLimit' ); /** @var RateLimit $rl */
 | 
						|
		$rl->setID( 'password_reset_' . Misc::getRemoteIPAddress() );
 | 
						|
		$rl->setAllowedCalls( 10 );
 | 
						|
		$rl->setTimeFrame( 900 ); //15 minutes
 | 
						|
 | 
						|
		$validator = new Validator();
 | 
						|
 | 
						|
		Debug::Text( 'Email: ' . $email, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
		if ( $rl->check() == false ) {
 | 
						|
			Debug::Text( 'Excessive reset password attempts... Preventing resets from: ' . Misc::getRemoteIPAddress() . ' for up to 15 minutes...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			sleep( 5 ); //Excessive password attempts, sleep longer.
 | 
						|
			$validator->isTrue( 'email', false, TTi18n::getText( 'Email address was not found in our database (z)' ) );
 | 
						|
		} else {
 | 
						|
			$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
			$ulf->getByHomeEmailOrWorkEmail( $email );
 | 
						|
			if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
				$user_obj = $ulf->getCurrent();
 | 
						|
				if ( $user_obj->getEnableLogin() == true ) { //Only allow password resets when logins are enabled.
 | 
						|
					//Check if company is using LDAP authentication, if so deny password reset.
 | 
						|
					if ( $user_obj->getCompanyObject()->getLDAPAuthenticationType() == 0 ) {
 | 
						|
						if ( $user_obj->sendPasswordResetEmail() == true ) {
 | 
						|
							Debug::Text( 'Found USER! ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
 | 
						|
							//Logout *current* session is they are currently logged in. Its a rare case of a user trying to reset their password while already logged in,
 | 
						|
							// but it has happened because they don't remember their password stored in a password manager.
 | 
						|
							$authentication = TTNew( 'Authentication' ); /** @var Authentication $authentication */
 | 
						|
							$authentication->setSessionID( getSessionID() );
 | 
						|
							$authentication->logout();
 | 
						|
 | 
						|
							$rl->delete(); //Clear password reset rate limit upon successful login.
 | 
						|
 | 
						|
							return $this->returnHandler( [ 'email_sent' => 1, 'email' => $email ] );
 | 
						|
						} else {
 | 
						|
							Debug::Text( 'ERROR: Unable to send password reset email, perhaps user record is invalid, or production mode is disabled?', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
							$validator->isTrue( 'email', false, TTi18n::getText( 'Unable to reset password, please contact your administrator for more information' ) );
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						Debug::Text( 'LDAP Authentication is enabled, password reset is disabled! ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
						$validator->isTrue( 'email', false, TTi18n::getText( 'Please contact your administrator for instructions on changing your password' ) . ' (LDAP)' );
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					$validator->isTrue( 'email', false, TTi18n::getText( 'Email address was not found in our database (b)' ) );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				//Error
 | 
						|
				Debug::Text( 'DID NOT FIND USER! Returned: ' . $ulf->getRecordCount(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				$validator->isTrue( 'email', false, TTi18n::getText( 'Email address was not found in our database (a)' ) );
 | 
						|
 | 
						|
				//If password was incorrect, sleep for some specified period of time to help delay brute force attacks.
 | 
						|
				if ( PRODUCTION == true ) {
 | 
						|
					Debug::Text( 'Email address for password reset was incorrect, sleeping for random amount of time...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					usleep( rand( 750000, 1500000 ) );
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			Debug::text( 'Reset Password Failed! Attempt: ' . $rl->getAttempts(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			sleep( ( $rl->getAttempts() * 0.5 ) ); //If email is incorrect, sleep for some time to slow down brute force attacks.
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), [ 'error' => $validator->getErrorsArray() ], [ 'total_records' => 1, 'valid_records' => 0 ] );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Reset the password if users forgotten their password
 | 
						|
	 * @param $key
 | 
						|
	 * @param $password
 | 
						|
	 * @param $password2
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	function passwordReset( $key, $password, $password2 ) {
 | 
						|
		$rl = TTNew( 'RateLimit' ); /** @var RateLimit $rl */
 | 
						|
		$rl->setID( 'password_reset_' . Misc::getRemoteIPAddress() );
 | 
						|
		$rl->setAllowedCalls( 10 );
 | 
						|
		$rl->setTimeFrame( 900 ); //15 minutes
 | 
						|
 | 
						|
		$validator = new Validator();
 | 
						|
		if ( $rl->check() == false ) {
 | 
						|
			Debug::Text( 'Excessive password reset attempts... Preventing resets from: ' . Misc::getRemoteIPAddress() . ' for up to 15 minutes...', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			sleep( 5 ); //Excessive password attempts, sleep longer.
 | 
						|
		} else {
 | 
						|
			$ulf = TTnew( 'UserListFactory' ); /** @var UserListFactory $ulf */
 | 
						|
			Debug::Text( 'Key: ' . $key, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			$ulf->getByPasswordResetKey( $key );
 | 
						|
			if ( $ulf->getRecordCount() == 1 ) {
 | 
						|
				Debug::Text( 'FOUND Password reset key! ', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				$user_obj = $ulf->getCurrent(); /** @var UserFactory $user_obj */
 | 
						|
				if ( $user_obj->checkPasswordResetKey( $key ) == true ) {
 | 
						|
					//Logout *current* session is they are currently logged in. Its a rare case of a user trying to reset their password while already logged in,
 | 
						|
					// but it has happened because they don't remember their password stored in a password manager.
 | 
						|
					$authentication = TTNew( 'Authentication' ); /** @var Authentication $authentication */
 | 
						|
					$authentication->setSessionID( getSessionID() );
 | 
						|
					$authentication->logout();
 | 
						|
 | 
						|
					//Make sure passwords match
 | 
						|
					Debug::Text( 'Change Password Key: ' . $key, __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					if ( $password != '' && trim( $password ) === trim( $password2 ) ) {
 | 
						|
						//Change password
 | 
						|
						$user_obj->setPassword( $password ); //Password reset key is cleared when password is changed.
 | 
						|
						$user_obj->setPasswordUpdatedDate( time() ); //Since the user isn't logged in, we have to manually force the PasswordUpdatedDate here to ensure its no longer -1 (compromised)
 | 
						|
						$user_obj->setEnableClearPasswordResetData( true ); //Clear any outstanding password reset key now that it has been used successfully.
 | 
						|
						$user_obj->setMultiFactorType( 0 ); //Disable MFA for this user when resetting password.
 | 
						|
						if ( $user_obj->isValid() ) {
 | 
						|
							$user_obj->Save( false );
 | 
						|
							Debug::Text( 'Password Change succesful!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
							TTLog::addEntry( $user_obj->getID(), 20, TTi18n::getText( 'Password Reset - Web (Completed)' ), $user_obj->getID(), $user_obj->getTable() );
 | 
						|
 | 
						|
							//Logout all sessions for this user when password is successfully reset.
 | 
						|
							$authentication = TTNew( 'Authentication' ); /** @var Authentication $authentication */
 | 
						|
							$authentication->logoutUser( $user_obj->getId() );
 | 
						|
							unset( $user_obj );
 | 
						|
 | 
						|
							return $this->returnHandler( true );
 | 
						|
						} else {
 | 
						|
							$validator->merge( $user_obj->Validator ); //Make sure we display any validation errors like password too weak.
 | 
						|
						}
 | 
						|
					} else {
 | 
						|
						$validator->isTrue( 'password', false, TTi18n::getText( 'Passwords do not match' ) );
 | 
						|
					}
 | 
						|
					//Do this once a successful key is found, so the user can get as many password change attempts as needed.
 | 
						|
					$rl->delete(); //Clear password reset rate limit upon successful reset.
 | 
						|
				} else {
 | 
						|
					Debug::Text( 'DID NOT FIND Valid Password reset key!', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
					$validator->isTrue( 'password', false, TTi18n::getText( 'Password reset key is invalid, please try resetting your password again.' ) );
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				Debug::Text( 'DID NOT FIND Valid Password reset key! (b)', __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
				$validator->isTrue( 'password', false, TTi18n::getText( 'Password reset key is invalid, please try resetting your password again.' ) . ' (b)' );
 | 
						|
			}
 | 
						|
 | 
						|
			Debug::text( 'Password Reset Failed! Attempt: ' . $rl->getAttempts(), __FILE__, __LINE__, __METHOD__, 10 );
 | 
						|
			sleep( ( $rl->getAttempts() * 0.5 ) ); //If email is incorrect, sleep for some time to slow down brute force attacks.
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( false, 'VALIDATION', TTi18n::getText( 'INVALID DATA' ), [ 'error' => $validator->getErrorsArray() ], [ 'total_records' => 1, 'valid_records' => 0 ] );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Sends a refreshed CSRF token cookie in case it expires prior to the user clicking the login button. This helps avoid showing an error message and triggering a full browser refresh.
 | 
						|
	 */
 | 
						|
	function sendCSRFTokenCookie() {
 | 
						|
		return $this->returnHandler( sendCSRFTokenCookie() );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Ping function is also in APIMisc for when the session timesout is valid.
 | 
						|
	 * Ping no longer can tell if the session is timed-out, must use "isLoggedIn(FALSE)" instead.
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	function Ping() {
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Used to mark notifications as read from things like tracking pixels in notification emails.
 | 
						|
	 *    Authentication is not required for this, but the user_id, type_id and object_id must all match and would be very unlikely to guess. Plus its pretty benign even if they do.
 | 
						|
	 * @param string $user_id UUID
 | 
						|
	 * @param int $type_id INT
 | 
						|
	 * @param string $object_id UUID
 | 
						|
	 * @return bool
 | 
						|
	 * @throws DBError
 | 
						|
	 */
 | 
						|
	function markNotificationAsRead( $user_id, $type_id, $object_id ) {
 | 
						|
		if ( TTUUID::isUUID( $user_id ) == false || TTUUID::isUUID( $object_id ) == false || empty( $type_id ) ) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return NotificationFactory::updateStatusByObjectIdAndObjectTypeId( (int)$type_id, TTUUID::castUUID( $object_id ), TTUUID::castUUID( $user_id ) );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return array|bool
 | 
						|
	 */
 | 
						|
	function removeAllTrustedDevices() {
 | 
						|
		if ( $this->isLoggedIn() == false ) {
 | 
						|
			return $this->returnHandler( false );
 | 
						|
		}
 | 
						|
 | 
						|
		$atdlf = TTnew( 'AuthenticationTrustedDeviceListFactory' ); /** @var AuthenticationTrustedDeviceListFactory $atdlf */
 | 
						|
		$atdlf->getByUserId( $this->getCurrentUserObject()->getId() );
 | 
						|
		if ( $atdlf->getRecordCount() > 0 ) {
 | 
						|
			foreach ( $atdlf as $atd_obj ) { /** @var AuthenticationTrustedDeviceFactory $atd_obj */
 | 
						|
				$atd_obj->setDeleted( true );
 | 
						|
				if ( $atd_obj->isValid() ) {
 | 
						|
					$atd_obj->Save();
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if ( isset( $_COOKIE['TrustedDevice'] ) ) {
 | 
						|
			//Destroy the current TrustedDevice cookie.
 | 
						|
			setcookie( 'TrustedDevice', '', ( time() - ( 3600 * 34 ) ), Environment::getCookieBaseURL() );
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->returnHandler( true );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param $delete_all_sessions
 | 
						|
	 * @return array|bool
 | 
						|
	 */
 | 
						|
	function logoutAllSessions( $delete_all_sessions = false ) {
 | 
						|
		if ( $this->isLoggedIn() == false ) {
 | 
						|
			return $this->returnHandler( false );
 | 
						|
		}
 | 
						|
 | 
						|
		global $authentication;
 | 
						|
 | 
						|
		if ( $delete_all_sessions === true ) {
 | 
						|
			$authentication->logoutUser( $this->getCurrentUserObject()->getId(), null, null );
 | 
						|
		} else {
 | 
						|
			$authentication->logoutUser( $this->getCurrentUserObject()->getId() );
 | 
						|
		}
 | 
						|
 | 
						|
 | 
						|
		return $this->returnHandler( true );
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
?>
 |