Location: PHPKode > scripts > Authenticator_ciacob > Authenticator.php
<?php

	/**
	* "Configurable module that provides login and related functionality."
    * @author: Claudius Tiberiu Iacob <hide@address.com>
    * @license: Creative Commons Attribution Share Alike - Claudius Tiberiu Iacob 2009
	*/
	class Authenticator extends ParamsProxy {

		const NO_ERROR = 0;
		const SESSION_UID_EXPIRED = 1;
		const INVALID_SESSION_UID = 2;
		const IP_HAS_CHANGED = 3;
		const UNKNOWN_USER_PASS_COMBO = 4;
		const NOT_YOUR_ROLE = 5;
		const IP_IS_BANNED = 6;
		const MASTER_NOT_SET = 7;
		const CANNOT_REDEFINE_MASTER = 8;
		const ILLEGAL_USER_NAME = 9;
		const ILLEGAL_EMAIL = 10;
		const ILLEGAL_PASSWORD = 11;
		const ILLEGAL_SECURITY_QUESTION = 12;
		const ILLEGAL_SECURITY_ANSWER = 13;
		const OPEN_REGISTRATION_NOT_SUPPORTED = 14;
		const NOT_RECOGNIZED_AS_SUPERUSER = 15;
		const IVALID_CONFIRMATION_UID = 16;
		const EXPIRED_CONFIRMATION_UID = 17;
		const USER_NOT_AUTHENTICATED = 18;
		const USER_HAS_NOT_CONFIRMED_REGISTRATION = 19;
		const INVALID_OLD_PASSWORD = 20;
		const INVALID_NEW_PASSWORD = 21;
		const UNKNOWN_EMAIL = 22;
		const SECURITY_QUESTION_NOT_SUPPORTED = 23;
		const WRONG_EMAIL_OR_SECURITY_ANSWER = 24;
		const NOT_RECOGNIZED_AS_MASTER = 25;

		const PASSWORD = "PASSWORD";
		const SECURITY_QUESTION = "SECURITY_QUESTION";
		const SECURITY_ANSWER = "SECURITY_ANSWER";
		const SUPER_USER_PASSWORD = "SUPER_USER_PASSWORD";
		const CONFIRMATION_UID = "CONFIRMATION_UID";

		const ILLEGAL_SESSION_LIFETIME = "ILLEGAL_SESSION_LIFETIME";
		const MISSING_SESSION_LIFETIME = "MISSING_SESSION_LIFETIME";
		const ILLEGAL_BLACKLIST_TIMEOUT = "ILLEGAL_BLACKLIST_TIMEOUT";
		const MISSING_BLACKLIST_TIMEOUT = "MISSING_BLACKLIST_TIMEOUT";
		const ILLEGAL_MAX_ATTEMPTS = "ILLEGAL_MAX_ATTEMPTS";
		const MISSING_MAX_ATTEMPTS = "MISSING_MAX_ATTEMPTS";
		const ILLEGAL_BAN_TIME = "ILLEGAL_BAN_TIME";
		const MISSING_BAN_TIME = "MISSING_BAN_TIME";
		const ILLEGAL_CONFIRMATION_UID_LIFETIME = "ILLEGAL_CONFIRMATION_UID_LIFETIME";
		const ILLEGAL_DEFAULT_ROLE = "ILLEGAL_DEFAULT_ROLE";
		const ILLEGAL_REMOVE_SECURITY_QUESTION_AKNOLEDGMENT =
			"ILLEGAL_REMOVE_SECURITY_QUESTION_AKNOLEDGMENT";
		const ILLEGAL_REMOVE_PASSWORD_RECOVERY_AKNOLEDGMENT =
			"ILLEGAL_REMOVE_PASSWORD_RECOVERY_AKNOLEDGMENT";
		const ILLEGAL_REMOVE_OPEN_REGISTRATION_AKNOLEDGMENT =
			"ILLEGAL_REMOVE_OPEN_REGISTRATION_AKNOLEDGMENT";

        private static $instance = null;

        private $dbProxy = null;
        private $sessionLifetime = null;
        private $blacklistTimeout = null;
        private $maxAttempts = null;
        private $banTime = null;
        private $confirmationUidLifetime = null;
        private $defaultRole = null;
        private $provideSecurityQuestionSupport = true;
        private $providePasswordRecoveryMode = true;
        private $provideOpenRegistrationSupport = true;
        private $haveMaster = false;
        private $skipConfirmation = false;

        private $administratorToken = "administrator";
        private $masterToken = "master";
        private $iAmAwarePhrase = "I am aware this is irreversible";
        private $userToken = "user";
        private $authUsersTblName = "AUTHENTICATOR_USERS";
        private $userColName = "User";
        private $mailColName = "Email";
        private $sqColName = "Security_Question";
        private $sqaColName = "Security_Question_Answer";
        private $passColName = "Password";
        private $passHashColName = "Password_Hash";
        private $confUidColName = "Confirmation_UID";
        private $roleColName = "Role";
        private $lastIpColName = "Last_IP";
        private $lsUIDColName = "Last_Session_UID";
        private $authBlacklistTblName = "AUTHENTICATOR_BLACKLIST";
        private $ipColName = "IP";
        private $banExpColName = "Ban_Expire_Time";
        private $statusColName = "Status_Col_Name";
        private $failNumColName = "Failed_Authentications_Number";
        private $ffDateColName = "First_Failure_Date";
        private $banStatus = "banned";
        private $observingStatus = "observing";
        private $hashedPassMarker = "\n";
        private $dateFormat = "YmdHis";
        private $userNameFormat = null;
        private $emailFormat = null;
		private $passwordFormat = null;

        private $sesUidTimestampOffset = 32;
        private $confUidTimestampOffset = 16;
        private $minChars = 4;
        private $maxChars = 20;
        private $sQuestionMaxChars = 100;
        private $sAnswerMaxChars = 100;

        public static function getInstance () {
            if (!self::$instance instanceof self) {
                self::$instance = new self();
            }
            return self::$instance;
        }

        protected function __construct () {
			parent::__construct();
			$this->confirmationUidLifetime = 86400;
			$this->defaultRole = $this->userToken;
			parent::configure($this);
			$this->dbProxy = new DbProxy();
			$this->dbProxy->configure($this);
			$this->proofConfiguration ();
			$this->proofDbStructure();
			$this->proofDroppables ();
			$this->proofMasterPresence ();
			$this->userNameFormat = "/^[a-zA-Z0-9_]{" . $this->minChars . "," .
				$this->maxChars . "}\$/";
			$this->passwordFormat = "/^[^\\n\\r\\t\\x0\\x0b]{" . $this->minChars . "," .
				$this->maxChars . "}\$/";
			$this->emailFormat = ",^[a-z0-9!#\\x24%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#\\x24%&'*+/=?^_`{|}~-]+)*@localhost|(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+(?:[A-Z]{2}|aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel)\$,i";
		}

        private function __clone () {
            // disabling cloning
        }

        private function __wakeup () {
            // disabling serialization
        }

        public function configure ($instance) {
            // disabling the inherited ability to configure an arbitrary class instance
        }

        public function setSessionLifetime ($value) {
        	if (!is_int ($value)) {
        		trigger_error(Authenticator::ILLEGAL_SESSION_LIFETIME, E_USER_ERROR);
			}
        	if (($value < 300 || $value > 86400) && $value !== -1) {
        		trigger_error(Authenticator::ILLEGAL_SESSION_LIFETIME, E_USER_ERROR);
			}
			$this->sessionLifetime = $value;
        }

        public function setBlacklistTimeout ($value) {
        	if (!is_int ($value)) {
				trigger_error(Authenticator::ILLEGAL_BLACKLIST_TIMEOUT, E_USER_ERROR);
        	}
        	if (($value < 60 || $value > 3600) && $value !== -1) {
				trigger_error (Authenticator::ILLEGAL_BLACKLIST_TIMEOUT, E_USER_ERROR);
        	}
        	$this->blacklistTimeout = $value;
        }

        public function setMaxAttempts ($value) {
        	if (!is_int ($value)) {
        		trigger_error (Authenticator::ILLEGAL_MAX_ATTEMPTS, E_USER_ERROR);
			}
			if (($value < 3 || $value > 600) && $value !== -1) {
				trigger_error (Authenticator::ILLEGAL_MAX_ATTEMPTS, E_USER_ERROR);
			}
			$this->maxAttempts = $value;
        }

        public function setBanTime ($value) {
        	if (!is_int ($value)) {
        		trigger_error (Authenticator::ILLEGAL_BAN_TIME, E_USER_ERROR);
			}
			if (($value < 1800 || $value > 86400) && $value !== -1) {
				trigger_error (Authenticator::ILLEGAL_BAN_TIME, E_USER_ERROR);
			}
			$this->banTime = $value;
        }

        public function setConfirmationUidLifetime ($value) {
        	if (!is_int ($value)) {
				trigger_error (Authenticator::ILLEGAL_CONFIRMATION_UID_LIFETIME, E_USER_ERROR);
        	}
        	if ($value < 86400 || $value > 2678400) {
				trigger_error (Authenticator::ILLEGAL_CONFIRMATION_UID_LIFETIME, E_USER_ERROR);
			}
			$this->confirmationUidLifetime = $value;
        }

        public function setDefaultRole ($value) {
        	if (!is_string($value)) {
				trigger_error(Authenticator::ILLEGAL_DEFAULT_ROLE, E_USER_ERROR);
        	}
        	$value = trim ($value);
        	if ($value === "") {
				trigger_error(Authenticator::ILLEGAL_DEFAULT_ROLE, E_USER_ERROR);
        	}
        	$value = strtolower($value);
        	if ($value === $this->administratorToken || $value === $this->masterToken) {
				trigger_error(Authenticator::ILLEGAL_DEFAULT_ROLE, E_USER_ERROR);
        	}
        	$this->defaultRole = $value;
        }

        public function setRemoveSecurityQuestionSupport ($value) {
        	if (!is_string($value)) {
				trigger_error(Authenticator::ILLEGAL_REMOVE_SECURITY_QUESTION_AKNOLEDGMENT,
					E_USER_ERROR);
        	}
        	$value = trim ($value);
        	if ($value === "") {
				trigger_error(Authenticator::ILLEGAL_REMOVE_SECURITY_QUESTION_AKNOLEDGMENT,
					E_USER_ERROR);
			}
			if ($value !== $this->iAmAwarePhrase) {
				trigger_error(Authenticator::ILLEGAL_REMOVE_SECURITY_QUESTION_AKNOLEDGMENT,
					E_USER_ERROR);
			}
			$this->provideSecurityQuestionSupport = false;
        }

        public function setRemovePasswordRecoveryMode ($value) {
        	if (!is_string($value)) {
				trigger_error(Authenticator::ILLEGAL_REMOVE_PASSWORD_RECOVERY_AKNOLEDGMENT,
					E_USER_ERROR);
        	}
        	$value = trim ($value);
        	if ($value === "") {
				trigger_error(Authenticator::ILLEGAL_REMOVE_PASSWORD_RECOVERY_AKNOLEDGMENT,
					E_USER_ERROR);
			}
			if ($value !== $this->iAmAwarePhrase) {
				trigger_error(Authenticator::ILLEGAL_REMOVE_PASSWORD_RECOVERY_AKNOLEDGMENT,
					E_USER_ERROR);
			}
			$this->providePasswordRecoveryMode = false;
        }

        public function setRemoveOpenRegistrationSupport ($value) {
        	if (!is_string($value)) {
				trigger_error(Authenticator::ILLEGAL_REMOVE_OPEN_REGISTRATION_AKNOLEDGMENT,
					E_USER_ERROR);
        	}
        	$value = trim ($value);
        	if ($value === "") {
				trigger_error(Authenticator::ILLEGAL_REMOVE_OPEN_REGISTRATION_AKNOLEDGMENT,
					E_USER_ERROR);
			}
			if ($value !== $this->iAmAwarePhrase) {
				trigger_error(Authenticator::ILLEGAL_REMOVE_OPEN_REGISTRATION_AKNOLEDGMENT,
					E_USER_ERROR);
			}
			$this->provideOpenRegistrationSupport = false;
        }

		public function authenticate ($credentials, $userName=null, $userRole=null) {
			if (!$this->haveMaster) {
				return Authenticator::MASTER_NOT_SET;
			}
			$currentIp = $_SERVER["REMOTE_ADDR"];
			if ($this->isIPBanned ($currentIp)) {
				return Authenticator::IP_IS_BANNED;
			}
			if ($userName === null) {
				// authenticating by an unique session ID
				$sessionId = $credentials;
				$matchedSesId = DbProxy::FAILURE;
				if (strlen (trim ($sessionId)) === $this->sesUidTimestampOffset) {
					$matchedSesId = $this->dbProxy->matchSessionId($this->authUsersTblName,
						$this->lsUIDColName, $sessionId);
				}
				if ($matchedSesId !== DbProxy::FAILURE) {
					// this unique session ID exists
					$expiryTimeStamp = substr ($matchedSesId, $this->sesUidTimestampOffset);
					$expiryTime = $this->getTimeFromStamp($expiryTimeStamp);
					$nowTime = time();
					if ($nowTime > $expiryTime) {
						// this unique session ID has expired
						$this->dbProxy->eraseSessionId ($this->authUsersTblName,
							$this->lsUIDColName, $matchedSesId);
						return Authenticator::SESSION_UID_EXPIRED;
					} else {
						$lastIp = $this->dbProxy->getLastIp($this->authUsersTblName,
							$this->lastIpColName, $this->lsUIDColName, $matchedSesId);
						if ($currentIp !== $lastIp) {
							// session is valid but accessed from a different IP; could be a
							// MIT attack
							$this->dbProxy->eraseSessionId ($this->authUsersTblName,
								$this->lsUIDColName, $matchedSesId);
							return Authenticator::IP_HAS_CHANGED;
						}
						// valid login using an unique session ID
						$newSesId = $this->makeUniqueSesId();
						$this->dbProxy->updateSessionId($this->authUsersTblName,
							$this->lsUIDColName, $matchedSesId, $newSesId);
						return substr ($newSesId, 0, $this->sesUidTimestampOffset);
					}
				} else {
					// invalid unique session ID
					return Authenticator::INVALID_SESSION_UID;
				}
			} else {
				// authenticating by a user name and password
				if ($this->provideOpenRegistrationSupport) {
					$hasConfirmed = $this->dbProxy->hasConfirmedReg ($this->authUsersTblName,
						$this->userColName, $userName, $this->confUidColName);
					if (!$hasConfirmed) {
						// a self-registered user must confirm his registration prior to authenticating
						return Authenticator::USER_HAS_NOT_CONFIRMED_REGISTRATION;
					}
				}
				$pass = $credentials;
				if (!$this->providePasswordRecoveryMode) {
					$pass = $this->hashedPassMarker . $this->hashPass($pass);
				}
				$succes = $this->dbProxy->matchUserAndPass ($this->authUsersTblName,
					$this->userColName, $userName, $this->passColName, $pass);
				if (!$succes) {
					// this combination of user name and password cannot be found.
					$this->blackListIp ($currentIp);
					return Authenticator::UNKNOWN_USER_PASS_COMBO;
				} else {
					if ($userRole === null) {
						$userRole = $this->defaultRole;
					}
					$roleOk = $this->dbProxy->matchUserAndRole ($this->authUsersTblName,
						$this->userColName, $userName, $this->roleColName, $userRole);
					if (!$roleOk) {
						// this user tried to fake a different role
						$this->blackListIp ($currentIp);
						return Authenticator::NOT_YOUR_ROLE;
					}
					// valid login using an user name and password
					$newSesId = $this->makeUniqueSesId();
					$this->dbProxy->writeSessionId($this->authUsersTblName,
						$this->lsUIDColName, $newSesId, $this->userColName, $userName);
					$this->dbProxy->writeUserIP($this->authUsersTblName, $this->lastIpColName,
						$currentIp, $this->userColName, $userName);
					$this->dbProxy->delBlackListedIp ($this->authBlacklistTblName,
						$this->ipColName, $currentIp);
					return substr ($newSesId, 0, $this->sesUidTimestampOffset);
				}
			}
		}

		public function unAuthenticate ($sessionId) {
			$matchedSesId = DbProxy::FAILURE;
			if (strlen (trim ($sessionId)) === $this->sesUidTimestampOffset) {
				$matchedSesId = $this->dbProxy->matchSessionId($this->authUsersTblName,
					$this->lsUIDColName, $sessionId);
			}
			if ($matchedSesId !== DbProxy::FAILURE) {
				$this->dbProxy->eraseSessionId ($this->authUsersTblName,
					$this->lsUIDColName, $matchedSesId);
			}
		}

		public function register ($credentials, $userName=null, $userEmail=null, $userRole=null) {
			if (array_key_exists (Authenticator::CONFIRMATION_UID, $credentials)) {
				// completing registration for a self-registered user by returning the confirmation uid
				if (!$this->provideOpenRegistrationSupport) {
					// open registration support has been dropped
					return Authenticator::OPEN_REGISTRATION_NOT_SUPPORTED;
				}
				// open registration is available
				$confirmationUid = trim ($credentials[Authenticator::CONFIRMATION_UID]);
				$confirmationUid = $this->dbProxy->matchConfUid($this->authUsersTblName,
					$this->confUidColName, $confirmationUid);
				if ($confirmationUid === DbProxy::FAILURE) {
					// provided confirmation uid cannot be matched to any user
					return Authenticator::IVALID_CONFIRMATION_UID;
				}
				// provided confirmation uid has been matched to a user
				$confUidExpiryDate = substr($confirmationUid, $this->confUidTimestampOffset);
				$confUidExpiryTime = $this->getTimeFromStamp($confUidExpiryDate);
				if ($confUidExpiryTime < time()) {
					// confirmation uid exists but has expired
					$this->dbProxy->removeConfUid ($this->authUsersTblName, $this->confUidColName,
						$confirmationUid);
					return Authenticator::EXPIRED_CONFIRMATION_UID;
				}
				// successfully completing user self-registration
				$credentials = $this->dbProxy->getCredentialsForConfUid ($this->authUsersTblName,
					$this->userColName, $this->passColName, $this->roleColName, $this->confUidColName,
					$confirmationUid);
				$this->dbProxy->removeConfUid ($this->authUsersTblName, $this->confUidColName,
					$confirmationUid);
				$user = trim ($credentials[0][0]);
				$pass = trim ($credentials[0][1]);
				$role = trim ($credentials[0][2]);
				$sessionUid = $this->authenticate($pass, $user, $role);
				return $sessionUid;
			}
			// registering a user
			$userRole = trim ($userRole);
			if ($userRole === "") {
				$userRole = $this->defaultRole;
			}
			if ($this->haveMaster && $userRole === $this->masterToken) {
				// master can only be registered once
				return Authenticator::CANNOT_REDEFINE_MASTER;
			}
			$isValidMaster = false;
			$isValidAdmin = false;
			if (!$this->provideOpenRegistrationSupport && $this->haveMaster) {
				if (!array_key_exists(Authenticator::SUPER_USER_PASSWORD, $credentials)) {
					// users cannot register themselves and no super user password given
					return Authenticator::OPEN_REGISTRATION_NOT_SUPPORTED;
				}
			}
			if (!$this->haveMaster) {
				// first user self-registering is assummed to be the master
				$userRole = $this->masterToken;
				$isValidMaster = true;
				$this->skipConfirmation = true;
			} else {
				$suPass = trim ($credentials[Authenticator::SUPER_USER_PASSWORD]);
				if (!$this->providePasswordRecoveryMode) {
					$suPass = $this->hashedPassMarker . $this->hashPass($suPass);
				}
				$isValidMaster = $this->dbProxy->validateSuperUser($this->authUsersTblName,
					$this->passColName, $suPass, $this->roleColName, $this->masterToken);
				if (!$isValidMaster) {
					$isValidAdmin = $this->dbProxy->validateSuperUser($this->authUsersTblName,
						$this->passColName, $suPass, $this->roleColName, $this->administratorToken);
				}
			}
			if (!$isValidMaster && !$isValidAdmin) {
				// users cannot register themselves (open registration support is dropped) or given
				// super user password is invalid
				if (!$this->provideOpenRegistrationSupport) {
					return Authenticator::NOT_RECOGNIZED_AS_SUPERUSER;
				}
			}
			if (!$isValidMaster) {
				// only master can register special users (such as admins), no matter open registration
				// is available or not
				if ($userRole !== $this->defaultRole) {
					return Authenticator::NOT_RECOGNIZED_AS_MASTER;
				}
			}
			// registering an user
			$userName = trim ($userName);
			if (!$this->isLegitUserName ($userName)) {
				// given user name is unacceptable
				return Authenticator::ILLEGAL_USER_NAME;
			}
			$userEmail = trim ($userEmail);
			if (!$this->isLegitEmail ($userEmail)) {
				// given user e-mail is unacceptable
				return Authenticator::ILLEGAL_EMAIL;
			}
			$userPass = trim($credentials[Authenticator::PASSWORD]);
			if (!$this->isLegitPassword ($userPass)) {
				// given user password is unacceptable
				return Authenticator::ILLEGAL_PASSWORD;
			}
			if (!$this->providePasswordRecoveryMode) {
				// password recovery support is dropped, so irreversible hash the password
				$userPass = $this->hashedPassMarker . $this->hashPass($userPass);
			}
			if ($this->provideSecurityQuestionSupport) {
				// a security question and answer must be provided
				$userSQ = trim($credentials[Authenticator::SECURITY_QUESTION]);
				if (!$this->isLegitSQ ($userSQ)) {
					// given security question is unacceptable
					return Authenticator::ILLEGAL_SECURITY_QUESTION;
				}
				$userSQAnswer = trime($credentials[Authenticator::SECURITY_ANSWER]);
				if (!$this->isLegitSQA ($userSQAnswer)) {
					// given security answer is unacceptable
					return Authenticator::ILLEGAL_SECURITY_ANSWER;
				}
				// register user with a security question and password
				$this->dbProxy->registerUserWithSQ ($this->authUsersTblName,
					$this->userColName, $userName, $this->mailColName, $userEmail,
					$this->sqColName, $userSQ, $this->sqaColName, $userSQAnswer,
					$this->passColName, $userPass, $this->roleColName, $userRole);
			} else {
				// register user with password and no security question
				$this->dbProxy->registerUserWithoutSQ($this->authUsersTblName,
					$this->userColName, $userName, $this->mailColName, $userEmail,
					$this->passColName, $userPass, $this->roleColName, $userRole);
			}
			if ($this->provideOpenRegistrationSupport) {
				if (!array_key_exists(Authenticator::SUPER_USER_PASSWORD, $credentials)) {
					if ($this->skipConfirmation) {
						// master has just registered himself; we needn't provide any confirmation
						$this->skipConfirmation = false;
					} else {
						// have open registration and user has registered himself; provide confirmation
						// uid
						$confirmationUI = $this->makeConfUid ();
						$this->dbProxy->addConfUid ($this->authUsersTblName, $this->confUidColName,
							$confirmationUI, $this->userColName, $userName);
						return substr ($confirmationUI, 0, $this->confUidTimestampOffset);
					}
				}
			}
			// open registration support is dropped, user has been registered by a super user, just exit
			return Authenticator::NO_ERROR;
		}

		public function changePassword ($userName, $currentPassword, $newPassword) {
			if (!$this->haveMaster) {
				return Authenticator::MASTER_NOT_SET;
			}
			$isUserAuthenticated = $this->dbProxy->isUserAuthenticated($this->authUsersTblName,
				$this->userColName, $userName, $this->lsUIDColName);
			if (!$isUserAuthenticated) {
				// user cannot change his password unless authenticated
				return Authenticator::USER_NOT_AUTHENTICATED;
			}
			if ($this->provideOpenRegistrationSupport) {
				$hasConfirmed = $this->dbProxy->hasConfirmedReg ($this->authUsersTblName,
					$this->userColName, $userName, $this->confUidColName);
				if (!$hasConfirmed) {
					// a self-registered user cannot change password until they confirm registration
					return Authenticator::USER_HAS_NOT_CONFIRMED_REGISTRATION;
				}
			}
			// changing password for given user
			if (!$this->providePasswordRecoveryMode) {
				$currentPassword = $this->hashedPassMarker . $this->hashPass($currentPassword);
			}
			$isOldPassValid = $this->dbProxy->isOldPassValid($this->authUsersTblName,
				$this->userColName, $userName, $this->passColName, $currentPassword);
			if (!$isOldPassValid) {
				// old password doesn't match
				return Authenticator::INVALID_OLD_PASSWORD;
			}
			$isNewPassValid = $this->isLegitPassword($newPassword);
			if (!$isNewPassValid) {
				// new password is inacceptable
				return Authenticator::INVALID_NEW_PASSWORD;
			}
			if (!$this->providePasswordRecoveryMode) {
				// if password recovery mode is dropped, irreversibly hash the new password
				$newPassword = $this->hashedPassMarker . $this->hashPass($newPassword);
			}
			// changing user's password
			$this->dbProxy->changePassword($this->authUsersTblName, $this->userColName,
				$userName, $this->passColName, $currentPassword, $newPassword);
			return Authenticator::NO_ERROR;
		}

		public function retrievePassword ($userEmail, $securityAnswer=null) {
			if (!$this->haveMaster) {
				return Authenticator::MASTER_NOT_SET;
			}
			$userEmail = trim ($userEmail);
			if ($securityAnswer === null) {
				if ($this->provideSecurityQuestionSupport) {
					$sq = DbPoxy::getInstance()->getSQForEmail($this->authUsersTblName,
						$this->sqColName, $this->mailColName, $userEmail);
					if ($sq === DbProxy::FAILURE) {
						return Authenticator::UNKNOWN_EMAIL;
					}
					return $sq;
				}
			} else {
				if (!$this->provideSecurityQuestionSupport) {
					return Authenticator::SECURITY_QUESTION_NOT_SUPPORTED;
				}
				$match = $this->dbProxy->matchMailToSA($this->authUsersTblName,
					$this->sqaColName, $securityAnswer, $this->mailColName, $userEmail);
				if (!$match) {
					return Authenticator::WRONG_EMAIL_OR_SECURITY_ANSWER;
				}
			}
			if (!$this->providePasswordRecoveryMode) {
				$newPassword = $this->getRandAlphaNum($this->maxChars);
				$hashedPassword = $this->hashedPassMarker . $this->hashPass ($newPassword);
				$this->dbProxy->overwritePass ($this->authUsersTblName, $this->mailColName,
					$userEmail, $this->passColName, $hashedPassword);
				$test = $this->dbProxy->getPassForEmail($this->authUsersTblName, $this->passColName,
					$this->mailColName, $userEmail);
				if ($test === DbProxy::FAILURE) {
					return Authenticator::UNKNOWN_EMAIL;
				}
				return $newPassword;
			}
			$retrievedPass = $this->dbProxy->getPassForEmail($this->authUsersTblName,
				$this->passColName, $this->mailColName, $userEmail);
			if ($retrievedPass === DbProxy::FAILURE) {
				return Authenticator::UNKNOWN_EMAIL;
			}
			return $retrievedPass;
		}

		public function unblockIp ($ip, $superUserPass) {
			if (!$this->haveMaster) {
				return Authenticator::MASTER_NOT_SET;
			}
			if (!$this->providePasswordRecoveryMode) {
				$superUserPass = $this->hashedPassMarker . $this->hashPass($superUserPass);
			}
			$isValidMaster = $this->dbProxy->validateSuperUser($this->authUsersTblName,
				$this->passColName, $superUserPass, $this->roleColName, $this->masterToken);
			if (!$isValidMaster) {
				// only the master can unblock IPs
				return Authenticator::NOT_RECOGNIZED_AS_SUPERUSER;
			}
			// successfully unblocked this IP
			$this->dbProxy->delBlackListedIp ($this->authBlacklistTblName, $this->ipColName,
				$ip);
			return Authenticator::NO_ERROR;
		}

		private function proofConfiguration () {
			if ($this->sessionLifetime === null) {
				trigger_error(Authenticator::MISSING_SESSION_LIFETIME, E_USER_ERROR);
			}
			if ($this->blacklistTimeout === null) {
				trigger_error(Authenticator::MISSING_BLACKLIST_TIMEOUT, E_USER_ERROR);
			}
			if ($this->maxAttempts === null) {
				trigger_error(Authenticator::MISSING_MAX_ATTEMPTS, E_USER_ERROR);
			}
			if ($this->banTime === null) {
				trigger_error(Authenticator::MISSING_BAN_TIME, E_USER_ERROR);
			}
		}

		private function proofDbStructure () {
			$haveTables = $this->dbProxy->haveTable($this->authUsersTblName) &&
				$this->dbProxy->haveTable($this->authBlacklistTblName);
			if (!$haveTables) {
				$this->dbProxy->makeUsersTable($this->authUsersTblName, $this->userColName,
					$this->mailColName, $this->sqColName, $this->sqaColName, $this->passColName,
					$this->confUidColName, $this->roleColName, $this->lastIpColName,
					$this->lsUIDColName);
				$this->dbProxy->makeBlacklistTable($this->authBlacklistTblName,
					$this->ipColName, $this->banExpColName, $this->statusColName, $this->banStatus,
					$this->observingStatus, $this->failNumColName, $this->ffDateColName);
			}
		}

		private function proofDroppables () {
			if (!$this->provideSecurityQuestionSupport) {
				$droppedAlready = !$this->dbProxy->haveSqColumns (
					$this->authUsersTblName, $this->sqColName, $this->sqaColName);
				if (!$droppedAlready) {
					$this->dropSQSupport ();
				}
			}
			if (!$this->provideOpenRegistrationSupport) {
			 	$droppedAlready = !$this->dbProxy->haveConfUidColumn (
			 		$this->authUsersTblName, $this->confUidColName);
			 	if (!$droppedAlready) {
			 		$this->dropORSupport();
				}
			}
			if (!$this->providePasswordRecoveryMode) {
				$droppedAlready = $this->dbProxy->havePassHashColumn(
					$this->authUsersTblName, $this->passHashColName);
				if (!$droppedAlready) {
					$this->dropPRSupport();
				}
				$this->passColName = $this->passHashColName;
			}
		}

		private function dropSQSupport () {
			$this->dbProxy->lockTable($this->authUsersTblName);
			$this->dbProxy->dropSQSupport($this->authUsersTblName, $this->sqColName,
				$this->sqaColName);
			$this->dbProxy->unlock();
		}

		private function dropORSupport () {
			$this->dbProxy->lockTable($this->authUsersTblName);
			$this->dbProxy->dropORSupport($this->authUsersTblName, $this->confUidColName);
			$this->dbProxy->unlock();
		}

		private function dropPRSupport () {
			$this->dbProxy->lockTable($this->authUsersTblName);
			$passwordsNo = $this->dbProxy->countRows($this->authUsersTblName,
				$this->passColName);
			for ($offset = 0; $offset < $passwordsNo; $offset++) {
				$row = $this->dbProxy->retrieveOneRow($this->authUsersTblName, $this->userColName,
					$this->passColName, $offset);
				$pass = $row[1];
				if (strpos($pass, $this->hashedPassMarker) === 0) {
					continue;
				}
				$priKey = $row[0];
				$pass = $this->hashPass($pass);
				$pass = $this->hashedPassMarker . $pass;
				$this->dbProxy->updateOneRow($this->authUsersTblName, $this->userColName, $priKey,
					$this->passColName, $pass);
			}
			$this->dbProxy->renamePassColName($this->authUsersTblName, $this->passColName,
				$this->passHashColName);
			$this->dbProxy->unlock();
		}

		private function hashPass ($pass) {
			$pass = md5(strrev($pass) . substr($pass, floor(strlen($pass)/2)));
			return $pass;
		}

		private function getRandAlphaNum ($charsNo) {
			$ret = "";
			while (strlen ($ret) < $charsNo) {
				$a = chr (rand (48, 57));
				$b = chr (rand (65, 90));
				$c = chr (rand (97, 122));
				$arr = array ($a, $b, $c);
				$ret .= $arr[rand(0,2)];
			}
			return $ret;
		}

		private function makeUniqueSesId () {
			return $this->getRandAlphaNum ($this->sesUidTimestampOffset) .
				date ($this->dateFormat, (time() + $this->sessionLifetime));
		}

		private function blackListIp ($ip) {
			$failCount = 0;
			$lastFailDate = 0;
			$mustBeBanned = false;
			$data = $this->dbProxy->getBlacklistData($this->authBlacklistTblName,
				$this->failNumColName, $this->ffDateColName, $this->ipColName, $ip);
			if (count ($data) > 0) {
				$row = $data[0];
				if (count ($row) > 0){
					// this is not the first failed login attempt in a row
					$failCount = $row [0];
					$firstFailDate =  $row [1];
				}
			} else {
				// this is the first failed login attempt from this IP
				$failNum = 1;
				$firstFailDate = date ($this->dateFormat, time());
				$this->dbProxy->blacklistIP ($this->authBlacklistTblName, $this->ipColName,
					$ip, $this->statusColName, $this->observingStatus, $this->failNumColName,
					$failNum, $this->ffDateColName, $firstFailDate);
			}
			if ($failCount >= $this->maxAttempts - 1) {
				// max number of failed login attempts has been reached
				if ($this->blacklistTimeout !== -1) {
					$firstFailTime = $this->getTimeFromStamp($firstFailDate);
					$timeSinceFirstFail = time() - $firstFailTime;
					if ($timeSinceFirstFail <= $this->blacklistTimeout) {
						// max failed logins occured within the time limit for ban
						$mustBeBanned = true;
					} else {
						// max failed logins occured outside the ban time limit: reset counter/ticker
						$this->dbProxy->resetFailedLogins($this->authBlacklistTblName,
							$this->failNumColName, 1, $this->ffDateColName, date ($this->dateFormat,
								time()), $this->ipColName, $ip);
					}
				} else {
					// max failed logins occured, with no maximum time limit set
					$mustBeBanned = true;
				}
			} else {
				// there are still grace failed login attempts for this ip
				$this->dbProxy->countFailedLogin($this->authBlacklistTblName,
					$this->failNumColName, $failCount+1, $this->ipColName, $ip);
			}
			if ($mustBeBanned) {
				// ban this ip
				$this->dbProxy->banIp($this->authBlacklistTblName, $this->banExpColName,
					 date($this->dateFormat, time() + $this->banTime), $this->statusColName,
					 $this->banStatus, $this->ipColName, $ip);
			}
		}

		private function isIPBanned ($ip) {
			$isBanned = $this->dbProxy->isIpBanned($this->authBlacklistTblName, $this->ipColName,
				$ip, $this->statusColName, $this->banStatus);
			if ($isBanned) {
				$isBanExpired = false;
				$expiryDate = $this->dbProxy->getBanExpiry($this->authBlacklistTblName,
					$this->banExpColName, $this->ipColName, $ip);
				$expiryTime = $this->getTimeFromStamp($expiryDate);
				if (time() > $expiryTime) {
					$isBanExpired = true;
				}
				if ($isBanExpired) {
					$this->dbProxy->delBlackListedIp ($this->authBlacklistTblName,
						$this->ipColName, $ip);
					return false;
				}
				return true;
			}
			return false;
		}

		private function getTimeFromStamp ($stamp) {
			$stamp = preg_replace('/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/',
				"$1-$2-$3 $4:$5:$6", $stamp);
			$time = strtotime($stamp);
			return $time;
		}

		private function proofMasterPresence () {
			$this->haveMaster =  $this->dbProxy->haveMaster ($this->authUsersTblName,
				$this->userColName, $this->roleColName, $this->masterToken);
		}

		private function isLegitUserName ($userName) {
			$pattern = $this->userNameFormat;
			return (preg_match($pattern, $userName) === 1);
		}

		private function isLegitEmail ($email) {
			$pattern = $this->emailFormat;
			return (preg_match($pattern, $email) === 1);
		}

		private function isLegitPassword ($pass) {
			$pattern = $this->passwordFormat;
			return (preg_match($pattern, $pass) === 1);
		}

		private function isLegitSQ ($sQuestion) {
			return ($sQuestion === "" || strlen ($sQuestion) > $this->sQuestionMaxChars);
		}

		private function isLegitSQA ($sAnswer) {
			return ($sAnswer === "" || strlen ($sAnswer) > $this->$sAnswerMaxChars);
		}

		private function makeConfUid () {
			return $this->getRandAlphaNum ($this->confUidTimestampOffset) .
				date ($this->dateFormat, (time() + $this->sessionLifetime));
		}
	}
?>
Return current item: Authenticator_ciacob