Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+370 -470
View File
@@ -5,7 +5,8 @@
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 13 Mar 2013
* @since 13 May 2017
* @version SVN_VERSION_NUMBER
*/
namespace IPS;
@@ -22,512 +23,309 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _Login
{
const LOGIN_FRONT = 1;
const LOGIN_ACP = 2;
const LOGIN_REGISTRATION_FORM = 3;
const LOGIN_REAUTHENTICATE = 4;
const LOGIN_UCP = 5;
const AUTH_TYPE_USERNAME = 1;
const AUTH_TYPE_EMAIL = 2;
/**
* @brief Handlers
*/
public static $handlers = NULL;
/**
* @brief All handlers
*/
public static $allHandlers = NULL;
const TYPE_USERNAME_PASSWORD = 1;
const TYPE_BUTTON = 2;
/**
* Get Handlers
*
* @param bool $all Force fetching of all enabled login handlers
* @return array
*/
public static function handlers( $all=FALSE )
{
/* Do we need all handlers? */
if( $all === TRUE )
{
if( static::$allHandlers !== NULL )
{
return static::$allHandlers;
}
foreach ( \IPS\Db::i()->select( '*', 'core_login_handlers', 'login_enabled=1', 'login_order' ) as $row )
{
try
{
static::$allHandlers[ $row['login_key'] ] = \IPS\Login\LoginAbstract::constructFromData( $row );
}
catch ( \RuntimeException $e ) { /* Skip over any which error (may happen if they haven't been updated for 4.x for example */ }
}
if ( \IPS\Dispatcher::hasInstance() === TRUE )
{
if( \IPS\Dispatcher::i()->controllerLocation == 'front' )
{
static::$handlers = static::$allHandlers;
}
}
return static::$allHandlers;
}
/* Fetch the appropriate handlers */
if ( static::$handlers === NULL )
{
if ( \IPS\Dispatcher::i()->controllerLocation === 'front' )
{
if ( isset( \IPS\Data\Store::i()->loginHandlers ) )
{
$rows = \IPS\Data\Store::i()->loginHandlers;
}
else
{
$rows = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_handlers', 'login_enabled=1', 'login_order' ) );
\IPS\Data\Store::i()->loginHandlers = $rows;
}
}
else
{
$rows = \IPS\Db::i()->select( '*', 'core_login_handlers', 'login_enabled=1 AND login_acp=1', 'login_order' );
}
foreach ( $rows as $row )
{
try
{
static::$handlers[ $row['login_key'] ] = \IPS\Login\LoginAbstract::constructFromData( $row );
}
catch ( \RuntimeException $e ) { /* Skip over any which error (may happen if they haven't bee updated for 4.x for example */ }
}
if( \IPS\Dispatcher::i()->controllerLocation == 'front' )
{
static::$allHandlers = static::$handlers;
}
}
/* If we have no handlers, use the standard internal */
if ( empty( static::$handlers ) )
{
static::$handlers['Internal'] = \IPS\Login\LoginAbstract::load('internal');
}
return static::$handlers;
}
/**
* Return a single handler object
*
* @throws UnderflowException
* @return \IPS\Login
*/
public static function getHandler( $handler )
{
return \IPS\Login\LoginAbstract::constructFromData( \IPS\Db::i()->select( '*', 'core_login_handlers', array( 'login_key=?', $handler ) )->first() );
}
/**
* @brief URL
*/
protected $url = '';
/**
* @brief Handlers which use the 'Standard' log in form
*/
public $standardHandlers = array();
/**
* @brief Forms
*/
protected $forms = NULL;
/**
* @brief Show flag options (remember me, anonymous) on form?
*/
public $flagOptions = TRUE;
/**
* @brief Which handler was used?
*/
public $usedHandler = NULL;
public $url;
/**
* @brief Login form type
*/
public $type;
/**
* @brief Reauthenticating member
*/
public $reauthenticateAs;
/**
* Constructor
*
* @param \IPS\Http\Url $url The URL page for the login screen
* @param \IPS\Http\Url $url The URL page for the login screen
* @param \IPS\Http\Url $returnUrl The URL to send the user back to after login
* @param int $type One of the LOGIN_* constants
* @return void
*/
public function __construct( \IPS\Http\Url $url )
public function __construct( \IPS\Http\Url $url = NULL, $type=1 )
{
$this->url = $url;
}
/**
* @brief Force a URL to send to post-login
* @note Useful when you need to redirect the user to another URL that is not local to this installation
*/
public static $forcedRedirectUrl = NULL;
/**
* Fetch the URL to redirect to
*
* @return \IPS\Http\Url
*/
public static function getDestination()
{
/* Try and get a referrer... */
try
$this->type = $type;
if ( $type === static::LOGIN_REAUTHENTICATE or $type === static::LOGIN_UCP )
{
/* Are we forcing the user to be sent to a specific URL? */
if( static::$forcedRedirectUrl !== NULL )
{
return static::$forcedRedirectUrl;
}
/* Get the URL we need to redirect to */
if ( isset( \IPS\Request::i()->ref ) and $decoded = @base64_decode( \IPS\Request::i()->ref ) )
{
$ref = \IPS\Http\Url::createFromString( $decoded );
}
elseif ( isset( $_SERVER['HTTP_REFERER'] ) )
{
$ref = \IPS\Http\Url::createFromString( $_SERVER['HTTP_REFERER'] );
}
else
{
throw new \DomainException;
}
/* Make sure it's internal and to the front-end */
if ( !( $ref instanceof \IPS\Http\Url\Internal ) or $ref->base !== 'front' )
{
throw new \DomainException;
}
/* Strip the csrf and return */
return $ref->stripQueryString( 'csrfKey' );
}
/* And if anything goes wrong, just use the base URL */
catch ( \Exception $e )
{
return \IPS\Http\Url::internal('');
$this->reauthenticateAs = \IPS\Member::loggedIn();
}
}
/* !Methods */
/**
* Fetch the URL to redirect to following registration, if any
* Get methods
*
* @param \IPS\Member $member The member that just registered
* @return \IPS\Http\Url
* @return array
*/
public static function getRegistrationDestination( $member )
public static function methods()
{
foreach ( static::handlers() as $key => $handler )
if ( !isset( \IPS\Data\Store::i()->loginMethods ) )
{
if ( method_exists( $handler, 'getRegistrationDestination' ) )
{
return $handler->getRegistrationDestination( $member );
}
\IPS\Data\Store::i()->loginMethods = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_methods', array( 'login_enabled=1' ) )->setKeyField('login_id') );
}
$ref = NULL;
if ( isset( \IPS\Request::i()->ref ) )
$return = array();
foreach ( \IPS\Data\Store::i()->loginMethods as $row )
{
try
{
$ref = \IPS\Http\Url::createFromString( base64_decode( \IPS\Request::i()->ref ) );
if ( !( $ref instanceof \IPS\Http\Url\Internal ) )
{
$ref = NULL;
}
$return[ $row['login_id'] ] = \IPS\Login\Handler::constructFromData( $row );
}
catch ( \Exception $e ) { }
catch ( \OutOfRangeException $e ) { }
}
if ( in_array( \IPS\Settings::i()->reg_auth_type, array( 'admin', 'admin_user' ) ) )
{
return \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=validating', 'front', 'register' );
}
return $ref ?: \IPS\Http\Url::internal( '' );
return $return;
}
/**
* Get Login Forms
* Get methods
*
* @param bool $acp TRUE=ACP login form, FALSE=front end login form
* @param bool $skipReferer If set to true we will skip adding the "referer" to the form, useful for the quick login popup where you want the same page to reload
* @return array
*/
public function forms( $acp=FALSE, $skipReferer=FALSE )
protected function _methods()
{
if ( $this->forms === NULL )
$methods = array();
foreach ( static::methods() as $k => $method )
{
$this->forms = array();
$standardTypes = 0;
foreach ( static::handlers() as $key => $handler )
{
if ( method_exists( $handler, 'loginForm' ) )
{
try
{
$this->forms[ $key ] = $handler->loginForm( $this->url, FALSE, $skipReferer ? $this->url : static::getDestination() );
}
catch( \BadMethodCallException $e )
{
/* The user may have installed a custom login handler, but not provided a template for the upgrader.
This results in a NO_TEMPLATE_FILE exception and blocks the upgrader completely. We should just skip
that login handler in this case */
continue;
}
}
else
{
$this->forms['_standard'] = NULL;
$standardTypes = $standardTypes | $handler->authTypes;
$this->standardHandlers[] = $key;
}
}
if ( $standardTypes !== 0 )
{
switch ( $standardTypes )
{
case static::AUTH_TYPE_USERNAME:
\IPS\Member::loggedIn()->language()->words['auth'] = \IPS\Member::loggedIn()->language()->addToStack( 'username', FALSE );
break;
case static::AUTH_TYPE_EMAIL:
\IPS\Member::loggedIn()->language()->words['auth'] = \IPS\Member::loggedIn()->language()->addToStack( 'email_address', FALSE );
break;
case static::AUTH_TYPE_USERNAME + static::AUTH_TYPE_EMAIL:
\IPS\Member::loggedIn()->language()->words['auth'] = \IPS\Member::loggedIn()->language()->addToStack( 'username_or_email', FALSE );
break;
}
$standardForm = new \IPS\Helpers\Form( "login__standard", 'login', $this->url );
$classname = 'IPS\Helpers\Form\Text';
if ( $standardTypes === static::AUTH_TYPE_EMAIL )
{
$classname = 'IPS\Helpers\Form\Email';
}
$standardForm->class = 'ipsForm_vertical';
$standardForm->add( new $classname( 'auth', NULL, TRUE, array( 'placeholder' => \IPS\Member::loggedIn()->language()->words['auth'], '_loginType' => $standardTypes, 'bypassProfanity' => TRUE ), NULL, NULL, NULL, 'auth' ) );
$standardForm->add( new \IPS\Helpers\Form\Password( 'password', NULL, TRUE, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack( 'password', FALSE ), 'bypassProfanity' => TRUE ), NULL, NULL, NULL, 'password' ) );
/* Are we adding the referer value to the form? */
if( !$skipReferer )
{
$standardForm->hiddenValues['ref'] = base64_encode( static::getDestination() );
}
if ( $this->flagOptions )
{
$standardForm->add( new \IPS\Helpers\Form\Checkbox( 'remember_me', TRUE ) );
if ( !\IPS\Settings::i()->disable_anonymous )
{
$standardForm->add( new \IPS\Helpers\Form\Checkbox( 'signin_anonymous' ) );
}
$standardForm->addButton( 'forgotten_password', 'link', \IPS\Http\Url::internal( 'app=core&module=system&controller=lostpass', 'front', 'lostpassword' ), 'ipsButton ipsButton_small ipsButton_fullWidth ipsButton_link' );
}
$this->forms['_standard'] = $standardForm;
}
}
return $this->forms;
}
/**
* @brief Login Flags
*/
public $flags = array( 'remember_me' => TRUE, 'signin_anonymous' => FALSE );
/**
* Check for successful authentication
*
* @return \IPS\Member|null
* @throws \IPS\Login\Exception
*/
public function authenticate()
{
if ( ( !isset( \IPS\Request::i()->cookie[ 'IPSSession' . ucfirst( \IPS\Dispatcher::i()->controllerLocation ) ] ) or \IPS\Request::i()->cookie[ 'IPSSession' . ucfirst( \IPS\Dispatcher::i()->controllerLocation ) ] != session_id() ) )
{
if ( !isset( \IPS\Request::i()->cookieCheck ) )
{
$_SESSION['_cookieCheck'] = TRUE; // Forces it to write the session so the above check will fail on the next load
\IPS\Output::i()->redirect( $this->url->setQueryString( 'cookieCheck', 1 ), NULL, 307 ); // 307 instructs the browser to resubmit the form as a POST request maintaining all the values from before
}
else
{
\IPS\Output::i()->error( 'login_err_no_cookies', '1S267/1', 403, '' );
if (
$this->type === static::LOGIN_FRONT
or $this->type === static::LOGIN_UCP
or ( $this->type === static::LOGIN_ACP and $method->acp )
or ( $this->type === static::LOGIN_REGISTRATION_FORM and $method->register )
or ( $this->type === static::LOGIN_REAUTHENTICATE and $method->canProcess( $this->reauthenticateAs ) and !( $method instanceof \IPS\Login\LoginAbstract ) )
) {
$methods[ $k ] = $method;
}
}
$handlers = static::handlers();
foreach ( $this->forms() as $handler => $form )
{
/* Pass to the handler */
$values = NULL;
if ( ( is_object( $form ) and $values = $form->values() ) or ( ucfirst( \IPS\Request::i()->loginProcess ) === ucfirst( $handler ) ) )
return $methods;
}
/**
* Get methods which use a username and password
*
* @return array
*/
public function usernamePasswordMethods()
{
$return = array();
foreach ( $this->_methods() as $method )
{
if ( $method->type() === static::TYPE_USERNAME_PASSWORD )
{
/* Set any flags */
foreach ( array_keys( $this->flags ) as $k )
{
if ( isset( $values[ $k ] ) )
{
$this->flags[ $k ] = $values[ $k ];
}
}
$return[ $method->id ] = $method;
}
}
return $return;
}
/**
* Should the username/password form ask for username or email address?
*
* @return array
*/
public function authType()
{
$authType = 0;
foreach ( $this->usernamePasswordMethods() as $method )
{
$authType = $authType | $method->authType();
}
return $authType;
}
/**
* Get methods which use a button
*
* @return array
*/
public function buttonMethods()
{
$return = array();
foreach ( $this->_methods() as $method )
{
if ( $method->type() === static::TYPE_BUTTON )
{
$return[ $method->id ] = $method;
}
}
return $return;
}
/* !Authentication */
/**
* Authenticate
*
* @param \IPS\Login\Handler\NULL $onlyCheck If provided, will only check the given method
* @return \IPS\Login\Success|NULL
* @throws \IPS\Login\Exception
*/
public function authenticate( \IPS\Login\Handler $onlyCheck = NULL )
{
try
{
if ( isset( \IPS\Request::i()->_processLogin ) )
{
\IPS\Session::i()->csrfCheck();
/* Authenticate */
$member = NULL;
try
/* Username/Password */
if ( \IPS\Request::i()->_processLogin === 'usernamepassword' )
{
if ( $handler === '_standard' )
$leastOffensiveException = NULL;
$success = NULL;
$fails = array();
foreach ( $this->usernamePasswordMethods() as $method )
{
$values['auth'] = mb_strtolower( $values['auth'] );
$member = $this->authenticateStandard( $values );
if ( !$onlyCheck or $method->id == $onlyCheck->id )
{
try
{
if ( $this->type === static::LOGIN_REAUTHENTICATE )
{
if ( $method->authenticatePasswordForMember( $this->reauthenticateAs, \IPS\Request::i()->password ) )
{
$member = $this->reauthenticateAs;
}
else
{
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $this->reauthenticateAs );
}
}
else
{
$member = $method->authenticateUsernamePassword( $this, \IPS\Request::i()->auth, \IPS\Request::i()->password );
if ( $member === TRUE )
{
$member = $this->reauthenticateAs;
}
}
if ( $member )
{
static::checkIfAccountIsLocked( $member, TRUE );
$success = new Login\Success( $member, $method, isset( \IPS\Request::i()->remember_me ), isset( \IPS\Request::i()->anonymous ) );
break;
}
}
catch ( \IPS\Login\Exception $e )
{
if ( $e->getCode() === \IPS\Login\Exception::BAD_PASSWORD and $e->member )
{
$fails[ $e->member->member_id ] = $e->member;
}
if ( $leastOffensiveException === NULL or $leastOffensiveException->getCode() < $e->getCode() )
{
$leastOffensiveException = $e;
}
}
}
}
foreach ( $fails as $failedMember )
{
if ( !$success or $success->member->member_id != $failedMember->member_id )
{
$failedLogins = is_array( $failedMember->failed_logins ) ? $failedMember->failed_logins : array();
$failedLogins[ \IPS\Request::i()->ipAddress() ][] = time();
$failedMember->failed_logins = $failedLogins;
$failedMember->save();
}
}
if ( $success )
{
return $success;
}
elseif ( $leastOffensiveException )
{
throw $leastOffensiveException;
}
else
{
$member = $handlers[ $handler ]->authenticate( is_object( $form ) ? $values : $this->url );
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::NO_ACCOUNT );
}
}
catch ( \IPS\Login\Exception $e )
/* Buttons */
elseif ( isset( $this->buttonMethods()[ \IPS\Request::i()->_processLogin ] ) and ( !$onlyCheck or $onlyCheck->id == $this->buttonMethods()[ \IPS\Request::i()->_processLogin ]->id ) )
{
/* Check if the account is locked and throw that error rather than a bad password error first */
if ( $e->getCode() === \IPS\Login\Exception::BAD_PASSWORD )
{
$this->checkIfAccountIsLocked( $e->member );
}
$method = $this->_methods()[ \IPS\Request::i()->_processLogin ];
/* If we're still here, throw the error we got */
throw $e;
}
/* If we passed, log in! */
if ( $member->member_id )
{
/* Set which handler processed it */
if ( $handler !== '_standard' ) // If _standard, is set in authenticateStandard()
if ( $member = $method->authenticateButton( $this ) )
{
$this->usedHandler = $handler;
}
//if( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation != 'setup' )
//{
/* Check if the account is locked */
$this->checkIfAccountIsLocked( $member );
if ( $this->type === static::LOGIN_REAUTHENTICATE and $member !== $this->reauthenticateAs )
{
throw new \IPS\Login\Exception( 'login_err_wrong_account', \IPS\Login\Exception::BAD_PASSWORD );
}
/* Remove old failed login attempts */
if ( \IPS\Settings::i()->ipb_bruteforce_period and ( \IPS\Settings::i()->ipb_bruteforce_unlock or !isset( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ) or $member->failed_logins[ \IPS\Request::i()->ipAddress() ] < \IPS\Settings::i()->ipb_bruteforce_attempts ) )
{
$removeLoginsOlderThan = \IPS\DateTime::create()->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) );
$failedLogins = $member->failed_logins;
/* The failed login data could potentially not be an array (i.e. a float) but as this code executes during the first
step of upgrading to 4.0 if we don't force it to be an array here we could end up with an error exception we can't
get past when attempting to upgrade. */
if( !is_array( $failedLogins ) )
{
$failedLogins = array();
}
if ( is_array( $failedLogins ) )
{
foreach ( $failedLogins as $ipAddress => $times )
{
foreach ( $times as $k => $v )
{
if ( $v < $removeLoginsOlderThan->getTimestamp() )
{
unset( $failedLogins[ $ipAddress ][ $k ] );
}
}
}
$member->failed_logins = $failedLogins;
}
else
{
$member->failed_logins = array();
}
$member->save();
}
/* If we're still here, the login was fine, so we can reset the count and process login */
if ( isset( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ) )
{
$failedLogins = $member->failed_logins;
unset( $failedLogins[ \IPS\Request::i()->ipAddress() ] );
$member->failed_logins = $failedLogins;
}
$member->last_visit = time();
$member->save();
//}
return $member;
static::checkIfAccountIsLocked( $member, TRUE );
return new Login\Success( $member, $method );
}
}
}
}
/* Still here? Just throw an exception */
return NULL;
}
/**
* Authenticate all 'Standard' forms
*
* @param array $values Values from form
* @return \IPS\Member
* @throws \IPS\Login\Exception
*/
public function authenticateStandard( $values )
{
$member = NULL;
$leastOffensiveException = NULL;
$handlers = static::handlers();
foreach ( $this->standardHandlers as $key )
{
try
/* Backwards Compatibility for login handlers created before 4.3 */
elseif ( isset( \IPS\Request::i()->loginProcess ) )
{
$member = $handlers[ $key ]->authenticate( $values );
$this->usedHandler = $key;
break;
}
catch ( \IPS\Login\Exception $e )
{
if ( $leastOffensiveException === NULL or $leastOffensiveException->getCode() > $e->getCode() )
foreach ( $this->buttonMethods() as $method )
{
$leastOffensiveException = $e;
if ( $method instanceof \IPS\Login\LoginAbstract and get_class( $method ) === 'IPS\Login\\' . mb_ucfirst( \IPS\Request::i()->loginProcess ) and ( !$onlyCheck or $method->id == $onlyCheck->id ) )
{
if ( $member = $method->authenticateButton( $this ) )
{
static::checkIfAccountIsLocked( $member, TRUE );
return new Login\Success( $member, $method, isset( \IPS\Request::i()->remember_me ), isset( \IPS\Request::i()->anonymous ) );
}
}
}
}
}
if ( $member === NULL )
catch ( \IPS\Login\Exception $e )
{
throw $leastOffensiveException;
}
else
{
return $member;
/* If we're about to say the password is incorrect, check if the account is locked and throw that error rather than a bad password error first */
if ( $e->getCode() === \IPS\Login\Exception::BAD_PASSWORD and $e->member )
{
static::checkIfAccountIsLocked( $e->member );
}
/* If we're still here, throw the error we got */
throw $e;
}
}
/* !Account Management Utility Methods */
/**
* Check if an account is locked
* After authentication (successful or failed) but before
* processing the login, check if the account is locked
*
* @param \IPS\Member $member The account
* @param \IPS\Member $member The account
* @param bool $succcess Boolean value indicating if the login was successfu. If TRUE, and the account is not locked, failed logins will be removed
* @return void
* @throws \Exception
*/
protected function checkIfAccountIsLocked( $member )
public static function checkIfAccountIsLocked( $member, $success = FALSE )
{
$unlockTime = static::accountUnlockTime( $member );
$unlockTime = $member->unlockTime();
if ( $unlockTime !== FALSE )
{
/* Notify the member if they've been locked */
@@ -543,57 +341,105 @@ class _Login
$location = \IPS\Request::i()->ipAddress();
}
\IPS\Email::buildFromTemplate( 'core', 'account_locked', array( $member, $location, isset( $unlockTime ) ? $unlockTime : NULL ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
$member->logHistory( 'core', 'login', array( 'type' => 'lock', 'count' => count( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ), 'unlockTime' => isset( $unlockTime ) ? $unlockTime->getTimestamp() : NULL ) );
}
if ( \IPS\Settings::i()->ipb_bruteforce_period and \IPS\Settings::i()->ipb_bruteforce_unlock )
{
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_locked_unlock', FALSE, array( 'pluralize' => array( $unlockTime->diff( new DateTime() )->format('%i') ) ) ) );
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_locked_unlock', FALSE, array( 'pluralize' => array( $unlockTime->diff( new DateTime() )->format('%i') ) ) ), \IPS\Login\Exception::ACCOUNT_LOCKED );
}
else
{
throw new \IPS\Login\Exception( 'login_err_locked_nounlock' );
throw new \IPS\Login\Exception( 'login_err_locked_nounlock', \IPS\Login\Exception::ACCOUNT_LOCKED );
}
}
elseif ( $success )
{
$failedLogins = is_array( $member->failed_logins ) ? $member->failed_logins : array();
unset( $failedLogins[ \IPS\Request::i()->ipAddress() ] );
$member->failed_logins = $failedLogins;
$member->save();
}
}
/**
* Check if an account is locked - returns FALSE if account is unlocked, an \IPS\DateTime object if the account is locked until a certain time, or TRUE if account is unlocked indefinitely
* Check if a given username is in use
* Returns string with error message or FALSE if not in use
*
* @param \IPS\Member $member The account
* @return \IPS\DateTime|bool
* @param string $username Desired username
* @param \IPS\Member $exclude If provided, that member will be excluded from the check
* @param \IPS\Member $admin Boolean value indicating if error message can include details about which login method has claimed it
* @return string|false
*/
public static function accountUnlockTime( $member )
public static function usernameIsInUse( $username, $exclude = NULL, $admin = FALSE )
{
if ( \IPS\Settings::i()->ipb_bruteforce_attempts and isset( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ) and count( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ) >= \IPS\Settings::i()->ipb_bruteforce_attempts )
/* Check locally */
$existingMember = \IPS\Member::load( $username, 'name' );
if ( $existingMember->member_id and ( !$exclude or $exclude->member_id != $existingMember->member_id ) )
{
if ( \IPS\Settings::i()->ipb_bruteforce_period and \IPS\Settings::i()->ipb_bruteforce_unlock )
{
$failedLogins = $member->failed_logins[ \IPS\Request::i()->ipAddress() ];
sort( $failedLogins );
while ( count( $failedLogins ) > \IPS\Settings::i()->ipb_bruteforce_attempts )
{
array_pop( $failedLogins );
}
$unlockTime = \IPS\DateTime::ts( array_pop( $failedLogins ) );
$unlockTime->add( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) );
/* If Unlock Time is in the past, return FALSE to avoid the exception and allow login */
if ( $unlockTime->getTimestamp() < time() )
{
return FALSE;
}
/* Otherwise that is what we're returning */
return $unlockTime;
}
return TRUE;
return \IPS\Member::loggedIn()->language()->addToStack('member_name_exists');
}
/* Check each handler */
foreach( static::methods() as $k => $handler )
{
if( $handler->usernameIsInUse( $username, $exclude ) === TRUE )
{
if( $admin )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_name_exists_admin', FALSE, array('sprintf' => array( $handler->_title ) ) );
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('member_name_exists');
}
}
}
/* Still here? We're good */
return FALSE;
}
/**
* Check if a given email address is in use
* Returns string with error message or FALSE if not in use
*
* @param string $email Desired email address
* @param \IPS\Member $exclude If provided, that member will be excluded from the check
* @param \IPS\Member $admin Boolean value indicating if error message can include details about which login method has claimed it
* @return string|false
*/
public static function emailIsInUse( $email, $exclude = NULL, $admin = FALSE )
{
/* Check locally */
$existingMember = \IPS\Member::load( $email, 'email' );
if ( $existingMember->member_id and ( !$exclude or $exclude->member_id != $existingMember->member_id ) )
{
return \IPS\Member::loggedIn()->language()->addToStack('member_email_exists');
}
/* Check each handler */
foreach( static::methods() as $k => $handler )
{
if( $handler->emailIsInUse( $email, $exclude ) === TRUE )
{
if( $admin )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_email_exists_admin', FALSE, array('sprintf' => array( $handler->_title ) ) );
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack('member_email_exists');
}
}
}
/* Still here? We're good */
return FALSE;
}
/* !Misc Utility Methods */
/**
* Compare hashes in fixed length, time constant manner.
*
@@ -622,7 +468,7 @@ class _Login
return $status === 0;
}
/**
* Return a random string
*
@@ -633,7 +479,11 @@ class _Login
{
$return = '';
if( function_exists( 'openssl_random_pseudo_bytes' ) )
if ( function_exists( 'random_bytes' ) )
{
$return = \substr( bin2hex( random_bytes( $length ) ), 0, $length );
}
elseif( function_exists( 'openssl_random_pseudo_bytes' ) )
{
$return = \substr( bin2hex( openssl_random_pseudo_bytes( ceil( $length / 2 ) ) ), 0, $length );
}
@@ -641,9 +491,59 @@ class _Login
/* Fallback JUST IN CASE */
if( !$return OR \strlen( $return ) != $length )
{
$return = \substr( md5( uniqid( microtime(), true ) ) . md5( uniqid( microtime(), true ) ), 0, $length );
$return = \substr( md5( uniqid( '', true ) ) . md5( uniqid( '', true ) ), 0, $length );
}
return $return;
}
protected static $_registrationType = NULL;
/**
* Registration Type
*
* @return string
*/
public static function registrationType()
{
if ( static::$_registrationType === NULL )
{
static::$_registrationType = \IPS\Settings::i()->allow_reg;
if ( static::$_registrationType == 1 )
{
static::$_registrationType = \IPS\Settings::i()->quick_register ? 'normal' : 'full';
}
if ( in_array( static::$_registrationType, array( 'normal', 'full' ) ) and !\IPS\Login\Handler::findMethod( 'IPS\Login\Handler\Standard' ) )
{
static::$_registrationType = 'disabled';
}
}
return static::$_registrationType;
}
/* !Backwards Compatibility for login handlers created before 4.3 */
/**
* Get Legacy Handlers
*
* @deprecated
* @return array
*/
public static function handlers()
{
$return = array();
foreach ( \IPS\Db::i()->select( '*', 'core_login_methods', array( 'login_enabled=1' ) ) as $method )
{
$method = \IPS\Login\Handler::constructFromData( $method );
if ( $method instanceof \IPS\Login\LoginAbstract )
{
$return[ mb_substr( get_class( $method ), 10 ) ] = $method;
}
}
return $return;
}
}