<?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));
}
}
?>