CoolFace
Apppublic

kenken999/php

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
userinfopage.php841 linesDownload Raw Back to classes
1<?php2require_once( getabspath("classes/editpage.php") );3 4class UserInfoPage extends EditPage {5 6	protected $cachedRecord = null;7 8	protected $newRecordData = array();9	protected $oldRecordData = array();10 11	protected $updatedSuccessfully = false;12 13 14	function __construct( &$params ) {15		parent::__construct( $params );16	}17 18 19	public static function processUserInfoPageSecurity( $table ) {20		if( !Security::providerUsersInDb( Security::currentProvider() ) ) {21			HeaderRedirect("menu");22		}23 24		Security::processLogoutRequest();25		$sessionLevel = Security::userSessionLevel();26		if( !Security::isGuest() && $sessionLevel === LOGGED_2FSETUP_PENDING ) {27			if( !Security::verifySafeCSRF() ) {28				return false;29			}30			return true;31		}32 33		Security::tryRelogin();34		if( isLogged() && !Security::isGuest() ) {35			return true;36		}37		if( Security::isGuest() ) {38			HeaderRedirect("menu");39			return false;40		}41		redirectToLogin();42		return false;43	}44 45	public function process() {46		global $globalEvents;47 48		if( $globalEvents->exists("BeforeProcessUserinfo") )49			$globalEvents->BeforeProcessUserinfo( $this );50 51		if( $this->action == "enable2f" ) {52			$this->enable2f( postvalue("method"), postvalue("value") );53			return;54		}55		if( $this->action == "disable2f" ) {56			$this->disable2f( postvalue( "method" ));57			return;58		}59		if( $this->action == "skip2f" ) {60			$this->skip2f();61			return;62		}63		if( $this->action == "prefer2f" ) {64			$this->prefer2f( postvalue( "method" ));65			return;66		}67 68		if( $this->action == "confirm2f" ) {69			$this->confirm2f( postvalue( "method" ), postvalue( "code" ) );70			return;71		}72 73		if( $this->action == "edited" && $this->mode === USERINFO_SIMPLE ) {74			$this->processDataInput();75			$this->reportFieldsSaveStatus();76			return;77		}78 79		if( $this->action == "generateSecret" ) {80			$this->regenerateTotp();81			return;82		}83 84 85		if( $this->pSet->hasCaptcha() )	{86			$this->displayCaptcha();87		}88 89		$this->prgReadMessage();90 91		$this->doCommonAssignments();92		$this->prepareBreadcrumbs();93		$this->prepareCollapseButton();94 95		$this->prepare2fControls();96		if( $this->mode === USERINFO_SIMPLE ) {97			$this->prepareReadonlyFields();98			$this->prepareEditControls();99		} else {100			$this->hideUserinfoItems();101		}102 103		$this->addButtonHandlers();104 105		$this->addCommonJs();106 107		$this->prepareTwoFactorData();108 109		$templateFile = $this->templatefile;110		if( $globalEvents->exists("BeforeShowUserinfo") )111			$globalEvents->BeforeShowUserinfo( $this->xt, $templateFile, $this->getCurrentrecordInternal(), $this );112 113		$this->display( $this->templatefile );114	}115 116	protected function prepareTwoFactorData() {117		$twofData = $this->loadTwofData();118		$this->pageData["twoFactorData"] = $twofData->serialize();119		$this->pageData["twoFactorSettings"] = Security::twoFactorSettings();120 121		$this->pageData["redirectUrl"] = $_SESSION["MyURL"]122			? $_SESSION["MyURL"]123			: GetTableLink("menu");124	}125 126 127	protected function regenerateTotp() {128 129		$twofData = $this->loadTwofData();130		$twofSettings =& Security::twoFactorSettings();131		if( !$twofSettings["available"] || !$twofSettings["types"]["totp"] ) {132			$this->send2fError( "TOTP authentication is not available" );133		}134		if( !$twofData->enable || $twofData->method != "totp" ) {135			$this->send2fError( "TOTP authentication is not enabled" );136		}137		$twofData->secret = generateTotpSecret();138		$twofData->confirmNeeded = true;139		storageSet( "2factor_update", $twofData->serialize() );140 141		//	return data to browser142		$this->send2fConfirmRequest( $twofData );143	}144 145	protected function buildNewRecordData() {146 147		// define temporary arrays. These are required for ASP conversion148		$evalues = array();149		$efilename_values = array();150		$blobfields = array();151 152		foreach( $this->pSet->getPageFields() as $f ) {153			// don't read username client value154			if ( $f == Security::usernameField() || $f == Security::extIdField())155				continue;156 157			$control = $this->getControl( $f, $this->id );158			$control->readWebValue( $evalues, $blobfields, NULL, NULL, $efilename_values );159		}160 161		foreach( $efilename_values as $ekey => $value ) {162			$evalues[ $ekey ] = $value;163		}164 165		$this->newRecordData = $evalues;166	}167 168	/**169	 * Process user data input and save it to the database table170	 */171	public function processDataInput() {172		global $globalEvents;173 174		//	CSRF protection175		if( !isPostRequest() )176			return false;177 178		$this->buildNewRecordData();179 180		if( !$this->recheckUserPermissions() ) {181			//	prevent the page from reading database values182			$this->oldRecordData = $this->newRecordData;183			$this->cachedRecord = $this->newRecordData;184			return false;185		}186 187		$this->oldRecordData = $this->getFieldControlsData();188 189		if( !$this->checkCaptcha() )190			return false;191 192		$dcUpdate = $this->getUpdateDataCommand();193		$prep = $this->dataSource->prepareSQL( $dcUpdate );194		$userInfoWhere = $prep["where"];195 196		if( $globalEvents->exists("BeforeEditUserinfo") ) {197			$usermessage = "";198 199			$ret = $globalEvents->BeforeEditUserinfo(200				$this->newRecordData,201				$userInfoWhere,202				$this->oldRecordData,203				$usermessage,204				$this205			);206 207			if( $usermessage != "" )208				$this->setMessage( $usermessage );209 210			if( !$ret )211				return false;212		}213 214		if( !$this->checkDeniedDuplicatedValues() )215			return false;216 217		$this->updatedSuccessfully = $this->dataSource->updateSingle( $dcUpdate, false );218		if( !$this->updatedSuccessfully ) {219			$this->setDatabaseError( $this->dataSource->lastError() );220			return false;221		}222		$this->auditLog( $dcUpdate->values );223 224		// clear cached record225		$this->cachedRecord = null;226 227		$this->ProcessFiles();228 229		$this->setSuccessfulEditMessage();230 231		Security::refreshUserdata();232		Security::refreshDisplayName();233 234		$this->callAfterSuccessfulSave();235 236		if( $globalEvents->exists("AfterEditUserinfo") ) {237			// Add missing values from oldRecordData to newRecordData238			foreach( $this->oldRecordData as $f => $v ) {239				if( !isset( $this->newRecordData[ $f ] ) )240					$this->newRecordData[ $f ] = $v;241			}242 243			$globalEvents->AfterEditUserinfo(244				$this->newRecordData,245				$userInfoWhere,246				$this->oldRecordData,247				$this248			);249		}250 251		return true;252	}253 254	/**255	 * @param String message256	 */257	public function setDatabaseError( $message )258	{259		if( $this->mode != EDIT_INLINE )260			$this->message = "<strong>"."Record was NOT edited"."</strong><br><br>".$message;261		else262			$this->message = "Record was NOT edited".". ".$message;263 264		$this->messageType = MESSAGE_ERROR;265	}266	267	/**268	 * Set a successful update message269	 */270	protected function setSuccessfulEditMessage()271	{272		if( $this->isMessageSet() )273			return;274 275		$this->messageType = MESSAGE_INFO;276		$this->setMessage( "<strong>"."User profile updated"."</strong>" );277 278		$_SESSION["message_userinfo"] = $this->message . "";279		$_SESSION["message_userinfo_type"] = $this->messageType;280	}281 282	/**283	 * Check if updated data contains duplicated values284	 * @return Boolean285	 */286	protected function checkDeniedDuplicatedValues() {287		return $this->checkDeniedDuplicatedForUpdate( $this->oldRecordData, $this->newRecordData );288	}289 290 291	public function getUpdateDataCommand() {292		$dc = $this->getSubsetDataCommand();293		$dc->values = &$this->newRecordData;294		return $dc;295	}296 297	/**298	 *	Call each control's afterSuccessfulSave method299	 */300	protected function callAfterSuccessfulSave() {301		foreach( $this->getPageFields() as $f ) {302			$this->getControl( $f, $this->id )->afterSuccessfulSave();303		}304	}305 306	/**307	 * send updated data to client308	 */309	protected function reportFieldsSaveStatus() {310		$returnJSON = array();311		$returnJSON['success'] = $this->updatedSuccessfully;312		$returnJSON['message'] = $this->message;313 314		if( !$this->isCaptchaOk )315			$returnJSON['wrongCaptchaFieldName'] = $this->getCaptchaFieldName();316 317		if( $this->updatedSuccessfully ) {318			$data = $this->getFieldControlsData();319			if( !$data )320				$data = $this->newRecordData;321 322			foreach( $this->pSet->getPageFields() as $f ) {323				if( !IsBinaryType( $this->pSet->getFieldType( $f ) ) )324					$controlValues[ $f ] = $data[ $f ];325			}326 327			$returnJSON['controlValues'] = $controlValues;328		}329 330		echo printJSON( $returnJSON );331		exit();332	}333 334	protected function prgReadMessage()335	{336		if( !$this->isSimpleMode() || !isset($_SESSION["message_userinfo"]) )337			return;338 339		$this->setMessage( $_SESSION["message_userinfo"] );340		$this->messageType = $_SESSION["message_userinfo_type"];341 342		unset($_SESSION["message_userinfo"]);343	}344 345	public function getCurrentRecordInternal() {346		if( !is_null( $this->cachedRecord ) )347			return $this->cachedRecord;348 349		$dc = $this->getSubsetDataCommand();350 351		$fetchedArray = $this->dataSource->getSingle( $dc )->fetchAssoc();352		$this->cachedRecord = $this->cipherer->DecryptFetchedArray( $fetchedArray );353 354		return $this->cachedRecord;355	}356 357	function getDataSourceFilterCriteria( $ignoreFilterField = "" ) {358		return Security::currentUserCondition();359	}360 361	protected function doCommonAssignments() {362 363		$this->headerCommonAssign();364		$this->setLangParams();365 366		//	display message367		$this->xt->assign("message_block", true);368		if( $this->isMessageSet() )369		{370			$this->xt->assign("message", $this->message );371			$this->xt->assign("message_class", $this->messageType == MESSAGE_ERROR ? "alert alert-danger" : "alert alert-success" );372		}373		else374		{375			$this->hideElement("message");376		}377 378		//	body["end"]	- this assignment is very important379		$this->assignFieldBlocksAndLabels();380		$this->assignBody();381	}382 383	protected function getFieldControlValues()384	{385		return $this->getFieldControlsData();386	}387 388	public function getEditFormat( $fName, $pSet = null ) {389		if( $fName == Security::usernameField() || $fName == Security::extIdField() ) {390			return EDIT_FORMAT_READONLY;391		}392		return parent::getEditFormat( $fName, $pSet );393	}394 395 396	public function prepareEditControls() {397		//	prepare values398		$data = $this->getFieldControlValues();399 400		foreach( $this->pSet->getPageFields() as $fName ) {401			$this->prepareEditControl( $fName, $data );402		}403 404		//	prepare userpic control?405	}406 407	public function getFieldControlsData() {408		return $this->getCurrentRecordInternal();409	}410 411	protected function recheckUserPermissions() {412		return true;413	}414 415	function element2Item( $name ) {416		if( $name == "message" ) {417			return array( "fields_message" );418		}419		return parent::element2Item( $name );420	}421 422	protected function prepare2fControls() {423		if( !Security::twoFactorAvailable() )424			return;425		$data = $this->getCurrentRecordInternal();426		if( !$data ) {427			return;428		}429		$this->hideItemType( 'twofactor_setup_comment' );430 431		$twofSettings = &Security::twoFactorSettings();432		$this->xt->assign( "twof_phone_value", $data[ $twofSettings["phoneField"] ] );433		$this->xt->assign( "twof_email_value", $data[ $twofSettings["emailField"] ] );434 435		$userOption = $data[ $twofSettings["twoFactorField"] ] + 0;436		$settings =& Security::twoFactorSettings();437		$enabledMethods = Security::twoFactorEnabledMethods( $userOption );438		$preferred = Security::twoFactorPreferredMethod( $userOption );439 440		foreach( Security::twoFactorAllMethods() as $m ) {441			442			if( Security::twoFactorMethodEnabled( $userOption, $m ) ) {443				$this->xt->assign( "twof_enabled".$m, true );444				if( $settings["required"] && count($enabledMethods) == 1 ) {445					$this->xt->assign( "twof_required".$m, true );446				}447			}448			if( $m == $preferred ) {449				$this->xt->assign( "twof_preferred".$m, true );450			}451 452		}453 454		if( Security::userSessionLevel() == LOGGED_2FSETUP_PENDING ) {455			$this->xt->assign( "twofactor_continue", true );456			if( !$twofSettings["required"] ) {457				$this->xt->assign( "twofactor_skip", true );458			}459			if( count( $enabledMethods ) ) {460				$this->hideItemType("twofactor_skip");461			} else {462				$this->hideItemType("twofactor_continue");463			}464		} 465 466	}467 468 469	/**470	 * Load current settings from the database471	 * @return TwoFactorData472	 */473	protected function loadTwofData() {474 475		$data = $this->getCurrentrecordInternal();476		if( !$data ) {477			return null;478		}479		$twofSettings =& Security::twoFactorSettings();480 481		$twofData = new TwoFactorData;482 483		$userOption = $data[ $twofSettings["twoFactorField"] ] + 0;484 485		$twofData->methods = Security::twoFactorEnabledMethods( $userOption );486		$twofData->preferred = Security::twoFactorPreferredMethod( $userOption );487		$twofData->secret = $data[ $twofSettings["codeField"] ];488		$twofData->email = $data[ $twofSettings["emailField"] ];489		$twofData->phone = $data[ $twofSettings["phoneField"] ];490		$twofData->required = $twofSettings["required"];491		return $twofData;492 493	}494 495 496	/**497	 * Verify if user input conflicts with project 2FA settings498	 */499	protected function verify2fSettings( $twofData ) {500 501		$twofSettings =& Security::twoFactorSettings();502 503		if( $twofData->enable && !$twofSettings["types"][ $twofData->method ]) {504			$this->send2fError( "Unsupported authentication type" );505			return false;506		}507 508		if( !$twofData->enable && $twofSettings["required"] ) {509			$twofData->enable = true;510		}511		return true;512	}513 514	/**515	 * 	read 2FA data from the request and save it to the session516	 */517	protected function enable2f( $method, $data ) {518 519		//	CSRF protection520		if( !isPostRequest() )521			return;522 523		if( !Security::twoFactorMethodAvailable($method) ) {524			echo "unknown two factor authentication method";525			exit();526		}527		if( $method != TWOFACTOR_APP && !$data ) {528			echo "no phone number or email provided";529			exit();530		}531 532 533		$twofData = $this->loadTwofData();534 535		if( $method == TWOFACTOR_APP ) {536			$twofData->secret = generateTotpSecret();537		} else if( $method == TWOFACTOR_EMAIL ) {538			$twofData->email = $data;539		} else if( $method == TWOFACTOR_PHONE ) {540			$twofData->phone = $data;541		}542 543		$twofData->methods[ $method ] = true;544		$twofData->code = generateUserCode( GetGlobalData("smsCodeLength", 6) );545		global $debug2Factor;546		if( $debug2Factor ) {547			$twofData->code = 333;548		}549		//	save data to session, send code to the user550		storageSet( "2factor_update", $twofData->serialize() );551 552		//	send verification code to the user553		$this->send2fCode( $method, $data, $twofData->code );554 555		//	return data to browser556		$this->send2fConfirmRequest( $method, $twofData );557	}558 559 560	protected function skip2f() {561 562		//	CSRF protection563		if( !isPostRequest() )564			return;565		566		$twofSettings =& Security::twoFactorSettings();567		if( !$twofSettings["required"] && Security::userSessionLevel() == LOGGED_2FSETUP_PENDING ) {568			$twofData = $this->loadTwofData();569			Security::elevateSession();570			Security::auditLoginSuccess();571			Security::callAfterLogin();572			$this->send2fSuccess( $twofData );573		} else {574			$this->send2fError("Two factor authentication is required");575		}576	}577 578	protected function disable2f( $method ) {579 580		//	CSRF protection581		if( !isPostRequest() )582			return;583 584		$twofData = $this->loadTwofData();585		unset( $twofData->methods[ $method ] );586		$this->save2fData( $twofData );587		$this->send2fSuccess( $twofData );588	}589 590	protected function prefer2f( $method ) {591		//	CSRF protection592		if( !isPostRequest() )593			return;594		$twofData = $this->loadTwofData();595		$twofData->preferred = Security::twoFactorPreferredMethod( 596			Security::getTwoFactorValue( $twofData->methods, $method )597		);598		$this->save2fData( $twofData );599		$this->send2fSuccess( $twofData );600	}601 602 603 604	protected function send2fError( $message, $status = 'error' ) {605		echo my_json_encode( array(606			"status" => $status,607			"message" => $message608		));609		exit();610	}611 612	/**613	 * send email or txt message with the code614	 */615	protected function send2fCode( $method, $data, $code ) {616 617		if( $method != TWOFACTOR_EMAIL && $method != TWOFACTOR_PHONE )618			return;619		$ret = Security::sendTwoFactorCode(620			$method,621			$data,622			$code623		);624		if( !$ret["success"] ) {625			$this->send2fError( "Error sending message. " . $ret["message"] );626		}627	}628 629	/**630	 * send response data to client, order it display confirm prompt631	 */632	protected function send2fConfirmRequest( $method, $twofData ) {633		$response = array(634			"status" => "confirm",635			"method" => $method636		);637		if( $method == TWOFACTOR_APP  ) {638			//	otpauth://totp/Project1:username?secret=...&issuer=Project1639			$response["secret"] = $twofData->secret;640			$response["totpUrl"] = $this->getTotpUrl( $twofData->secret );641		} else if( $method == TWOFACTOR_EMAIL ) {642			$response["email"] = $twofData->email;643		} else if( $method == TWOFACTOR_PHONE ) {644			$response["phone"] = $twofData->phone;645		}646		echo my_json_encode( $response );647		exit();648	}649 650	/**651	 * inform user on successful 2FA settings update652	 */653	protected function send2fSuccess( $twofData ) {654		$twofData->preferred = Security::twoFactorPreferredMethod( 655			Security::getTwoFactorValue( $twofData->methods, $twofData->preferred )656		);657		$response = array(658			"status" => "saved",659			"twoFactorData" => $twofData->serialize()660		);661		echo my_json_encode( $response );662		exit();663	}664 665	protected function getTotpUrl( $secret ) {666		$twofSettings =& Security::twoFactorSettings();667		$encodedProjectName = rawurlencode( $twofSettings["projectName"] );668		$encodedUsername = rawurlencode( $this->getUserName() );669		$encodedSecret = rawurlencode( $secret );670		return "otpauth://totp/" . $encodedProjectName . ":".$encodedUsername."?secret=". $encodedSecret ."&issuer=" . $encodedProjectName;671	}672 673	/**674	 * Check user-entered code against the one saved in session.675	 * Save new values in the database676	 */677	protected function confirm2f( $method, $code ) {678		$twofData = TwoFactorData::deserialize( storageGet("2factor_update") );679		if( !$twofData ) {680			$this->send2fError( "Session is lost. Refresh the page and try again." );681		}682		if( !$this->verify2fCode( $method, $twofData, $code ) ) {683			//	just wrong code, ask to reype684			$this->send2fError( "Wrong code", 'wrong' );685		}686 687		//	save data in the database688		$this->save2fData( $twofData );689		if( Security::userSessionLevel() != LOGGED_FULL ) {690			Security::elevateSession();691			Security::auditLoginSuccess();692			Security::callAfterLogin();693		}694		$this->send2fSuccess( $twofData );695	}696 697	protected function verify2fCode( $method, $twofData, $code ) {698		if( $method == TWOFACTOR_EMAIL || $method == TWOFACTOR_PHONE )  {699			return $twofData->code == $code;700		}701		if( $method == TWOFACTOR_APP ) {702			return $code == calculateTotpCode( $twofData->secret );703		}704	}705 706	protected function save2fData( $twofData ) {707		$twofSettings =& Security::twoFactorSettings();708 709		$dc = $this->getSubsetDataCommand();710 711		$dc->values[ $twofSettings["twoFactorField"] ] = Security::getTwoFactorValue( $twofData->methods, $twofData->preferred );712 713		if( $twofData->methods[ TWOFACTOR_EMAIL] ) {714			$dc->values[ $twofSettings["emailField"] ] = $twofData->email;715		}716		if( $twofData->methods[ TWOFACTOR_PHONE ] ) {717			$dc->values[ $twofSettings["phoneField"] ] = $twofData->phone;718		}719		if( $twofData->methods[ TWOFACTOR_APP] ) {720			$dc->values[ $twofSettings["codeField"] ] = $twofData->secret;721		}722		723		if( !$this->dataSource->updateSingle( $dc, false ) ) {724			$this->send2fError( $this->dataSource->lastError() );725		}726		$this->auditLog( $dc->values );727		Security::refreshUserdata();728 729	}730 731	function addCommonJs()732	{733		parent::addCommonJs();734		$this->AddJSFile("include/qrcode/qrcode2.js");735		$this->AddJSFile("include/qrcode/jquery.qrcode.js");736	}737 738	protected function getUserName() {739		$sessionLevel = Security::userSessionLevel();740		if( $sessionLevel === LOGGED_FULL ) {741			return Security::getUserName();742		}743		return Security::provisionalUsername();744 745	}746 747	public static function readPageModeFromRequest() {748		if( postvalue("mode") == '2factor' || Security::userSessionLevel() == LOGGED_2FSETUP_PENDING ) {749			return USERINFO_2FACTOR;750		}751		return USERINFO_SIMPLE;752	}753 754	/**755	 * Hide all items not needed for 2factor authentication setup756	 */757	protected function hideUserinfoItems() {758		$this->hideAllFormItems( 'grid' );759		$this->showItemType( 'twofactor_label' );760		if( Security::userSessionLevel() === LOGGED_2FSETUP_PENDING ) {761			$this->showItemType( 'twofactor_setup_comment' );762		} else {763			$this->showItemType( 'twofactor_comment' );764		}765 766		$this->showItemType( 'twofactor_settings' );767 768	}769 770	public function setTemplateFile() {771		return RunnerPage::setTemplateFile();772	}773 774	protected function prepareBreadcrumbs() {775		$this->xt->assign( "breadcrumb", true );776		$this->xt->assign( "crumb_home_link", runner_htmlspecialchars( GetLocalLink("menu") ) );777 778		$crumb = array();779		$crumb["crumb_title_span"] = true;780		$crumb["crumb_title"] = $this->getPageTitle( $this->pageType, $this->tName, $this->pSet );781		$breadcrumb = array( $crumb );782		$this->xt->assign_loopsection( "crumb", $breadcrumb );783	}784 785	public function auditLog( $values ) {786		$audit = GetAuditObject();787		if( !$audit ) {788			return;789		}790		$keys = array( Security::currentUserIdField() => Security::getUserId() );791		$values[ Security::currentUserIdField() ] = Security::getUserId();792		$audit->LogEdit( Security::loginTable(), $values, Security::currentUserData(), $keys );793	}794 795	public function getSingleRecordCommand() {796		return $this->getSubsetDataCommand();797	}798 799	function createProjectSettings() {800		$this->pSet = new ProjectSettings( $this->tName, $this->pageType, $this->pageName, $this->pageTable );801	}802}803 804class TwoFactorData {805	/**806	 * code to be sent to the user807	 * @var string808	 */809	public $code;810 811 812	public $methods;813	public $preferred;814 815	public $secret;816	public $email;817	public $phone;818	public $required;819 820 821	public function serialize() {822		$ret = array();823		$ret["code"] = $this->code;824		$ret["methods"] = $this->methods;825		$ret["preferred"] = $this->preferred;826		$ret["secret"] = $this->secret;827		$ret["email"] = $this->email;828		$ret["phone"] = $this->phone;829		$ret["required"] = $this->required;830		return $ret;831	}832 833	public static function deserialize( $params ) {834		$ret = new TwoFactorData;835		RunnerApply($ret, $params);836		return $ret;837	}838 839}840 841?>