Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
+123
-27
@@ -12,7 +12,7 @@
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -23,16 +23,49 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Login
|
||||
{
|
||||
/**
|
||||
* @brief Front end login form
|
||||
*/
|
||||
const LOGIN_FRONT = 1;
|
||||
|
||||
/**
|
||||
* @brief AdminCP login form
|
||||
*/
|
||||
const LOGIN_ACP = 2;
|
||||
|
||||
/**
|
||||
* @brief Login form shown on registration form
|
||||
*/
|
||||
const LOGIN_REGISTRATION_FORM = 3;
|
||||
|
||||
/**
|
||||
* @brief Requesting reauthentication
|
||||
*/
|
||||
const LOGIN_REAUTHENTICATE = 4;
|
||||
|
||||
/**
|
||||
* @brief Reauthentication required for account changes
|
||||
*/
|
||||
const LOGIN_UCP = 5;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Using username for login
|
||||
*/
|
||||
const AUTH_TYPE_USERNAME = 1;
|
||||
|
||||
/**
|
||||
* @brief Using email for login
|
||||
*/
|
||||
const AUTH_TYPE_EMAIL = 2;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Username/password form
|
||||
*/
|
||||
const TYPE_USERNAME_PASSWORD = 1;
|
||||
|
||||
/**
|
||||
* @brief Button form (i.e. for OAuth-style logins)
|
||||
*/
|
||||
const TYPE_BUTTON = 2;
|
||||
|
||||
/**
|
||||
@@ -54,7 +87,6 @@ class _Login
|
||||
* Constructor
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@@ -78,13 +110,8 @@ class _Login
|
||||
*/
|
||||
public static function methods()
|
||||
{
|
||||
if ( !isset( \IPS\Data\Store::i()->loginMethods ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->loginMethods = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_methods', array( 'login_enabled=1' ) )->setKeyField('login_id') );
|
||||
}
|
||||
|
||||
$return = array();
|
||||
foreach ( \IPS\Data\Store::i()->loginMethods as $row )
|
||||
foreach ( static::getStore() as $row )
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -94,6 +121,21 @@ class _Login
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login method Store
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getStore()
|
||||
{
|
||||
if ( !isset( \IPS\Data\Store::i()->loginMethods ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->loginMethods = iterator_to_array( \IPS\Db::i()->select( '*', 'core_login_methods', array( 'login_enabled=1' ), 'login_order' )->setKeyField( 'login_id' ) );
|
||||
}
|
||||
|
||||
return \IPS\Data\Store::i()->loginMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get methods
|
||||
@@ -202,7 +244,7 @@ class _Login
|
||||
{
|
||||
if ( $this->type === static::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
if ( $method->authenticatePasswordForMember( $this->reauthenticateAs, \IPS\Request::i()->password ) )
|
||||
if ( $method->authenticatePasswordForMember( $this->reauthenticateAs, \IPS\Request::i()->protect('password') ) )
|
||||
{
|
||||
$member = $this->reauthenticateAs;
|
||||
}
|
||||
@@ -213,7 +255,7 @@ class _Login
|
||||
}
|
||||
else
|
||||
{
|
||||
$member = $method->authenticateUsernamePassword( $this, \IPS\Request::i()->auth, \IPS\Request::i()->password );
|
||||
$member = $method->authenticateUsernamePassword( $this, \IPS\Request::i()->auth, \IPS\Request::i()->protect('password') );
|
||||
if ( $member === TRUE )
|
||||
{
|
||||
$member = $this->reauthenticateAs;
|
||||
@@ -223,7 +265,7 @@ class _Login
|
||||
if ( $member )
|
||||
{
|
||||
static::checkIfAccountIsLocked( $member, TRUE );
|
||||
$success = new Login\Success( $member, $method, isset( \IPS\Request::i()->remember_me ), isset( \IPS\Request::i()->anonymous ) );
|
||||
$success = new Login\Success( $member, $method, isset( \IPS\Request::i()->remember_me ), $member->members_bitoptions['is_anon'] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -246,7 +288,7 @@ class _Login
|
||||
{
|
||||
if ( !$success or $success->member->member_id != $failedMember->member_id )
|
||||
{
|
||||
$failedLogins = is_array( $failedMember->failed_logins ) ? $failedMember->failed_logins : array();
|
||||
$failedLogins = \is_array( $failedMember->failed_logins ) ? $failedMember->failed_logins : array();
|
||||
$failedLogins[ \IPS\Request::i()->ipAddress() ][] = time();
|
||||
$failedMember->failed_logins = $failedLogins;
|
||||
$failedMember->save();
|
||||
@@ -288,12 +330,12 @@ class _Login
|
||||
{
|
||||
foreach ( $this->buttonMethods() as $method )
|
||||
{
|
||||
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 ( $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 ) );
|
||||
return new Login\Success( $member, $method, isset( \IPS\Request::i()->remember_me ), $member->members_bitoptions['is_anon'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,7 +361,7 @@ class _Login
|
||||
* processing the login, check if the account is locked
|
||||
*
|
||||
* @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
|
||||
* @param bool $success Boolean value indicating if the login was successful. If TRUE, and the account is not locked, failed logins will be removed.
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -329,7 +371,7 @@ class _Login
|
||||
if ( $unlockTime !== FALSE )
|
||||
{
|
||||
/* Notify the member if they've been locked */
|
||||
if( count( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ) == \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
if( \count( $member->failed_logins[ \IPS\Request::i()->ipAddress() ] ) == \IPS\Settings::i()->ipb_bruteforce_attempts )
|
||||
{
|
||||
/* Can we get a physical location */
|
||||
try
|
||||
@@ -341,12 +383,14 @@ 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 ) );
|
||||
$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') ) ) ), \IPS\Login\Exception::ACCOUNT_LOCKED );
|
||||
$diffValue = $unlockTime->diff( new DateTime() )->format('%i') ?: 1;
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_locked_unlock', FALSE, array( 'pluralize' => array( $diffValue ) ) ), \IPS\Login\Exception::ACCOUNT_LOCKED );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -355,12 +399,27 @@ class _Login
|
||||
}
|
||||
elseif ( $success )
|
||||
{
|
||||
$failedLogins = is_array( $member->failed_logins ) ? $member->failed_logins : array();
|
||||
$failedLogins = \is_array( $member->failed_logins ) ? $member->failed_logins : array();
|
||||
unset( $failedLogins[ \IPS\Request::i()->ipAddress() ] );
|
||||
$member->failed_logins = $failedLogins;
|
||||
$member->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given username is allowed
|
||||
*
|
||||
* @param string $username Desired username
|
||||
* @return bool
|
||||
*/
|
||||
public static function usernameIsAllowed( string $username ): bool
|
||||
{
|
||||
if( \IPS\Settings::i()->username_characters and !preg_match( \IPS\Settings::i()->username_characters, $username ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given username is in use
|
||||
@@ -449,7 +508,7 @@ class _Login
|
||||
*/
|
||||
public static function compareHashes( $expected, $provided )
|
||||
{
|
||||
if ( !is_string( $expected ) || !is_string( $provided ) || $expected === '*0' || $expected === '*1' || $provided === '*0' || $provided === '*1' ) // *0 and *1 are failures from crypt() - if we have ended up with an invalid hash anywhere, we will reject it to prevent a possible vulnerability from deliberately generating invalid hashes
|
||||
if ( !\is_string( $expected ) || !\is_string( $provided ) || $expected === '*0' || $expected === '*1' || $provided === '*0' || $provided === '*1' ) // *0 and *1 are failures from crypt() - if we have ended up with an invalid hash anywhere, we will reject it to prevent a possible vulnerability from deliberately generating invalid hashes
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -463,7 +522,7 @@ class _Login
|
||||
$status = 0;
|
||||
for ( $i = 0; $i < $len; $i++ )
|
||||
{
|
||||
$status |= ord( $expected[ $i ] ) ^ ord( $provided[ $i ] );
|
||||
$status |= \ord( $expected[ $i ] ) ^ \ord( $provided[ $i ] );
|
||||
}
|
||||
|
||||
return $status === 0;
|
||||
@@ -479,11 +538,11 @@ class _Login
|
||||
{
|
||||
$return = '';
|
||||
|
||||
if ( function_exists( 'random_bytes' ) )
|
||||
if ( \function_exists( 'random_bytes' ) )
|
||||
{
|
||||
$return = \substr( bin2hex( random_bytes( $length ) ), 0, $length );
|
||||
}
|
||||
elseif( function_exists( 'openssl_random_pseudo_bytes' ) )
|
||||
elseif( \function_exists( 'openssl_random_pseudo_bytes' ) )
|
||||
{
|
||||
$return = \substr( bin2hex( openssl_random_pseudo_bytes( ceil( $length / 2 ) ) ), 0, $length );
|
||||
}
|
||||
@@ -497,6 +556,9 @@ class _Login
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached registration type
|
||||
*/
|
||||
protected static $_registrationType = NULL;
|
||||
|
||||
/**
|
||||
@@ -515,7 +577,7 @@ class _Login
|
||||
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' ) )
|
||||
if ( \in_array( static::$_registrationType, array( 'normal', 'full' ) ) and !\IPS\Login\Handler::findMethod( 'IPS\Login\Handler\Standard' ) )
|
||||
{
|
||||
static::$_registrationType = 'disabled';
|
||||
}
|
||||
@@ -524,6 +586,40 @@ class _Login
|
||||
return static::$_registrationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a user out
|
||||
*
|
||||
* @param \IPS\Http\Url $redirectUrl The URL the user will be redirected to after logging out
|
||||
* @return void
|
||||
*/
|
||||
public static function logout( \IPS\Http\Url $redirectUrl = NULL )
|
||||
{
|
||||
/* Do not allow the login_key to be re-used */
|
||||
if ( isset( \IPS\Request::i()->cookie['device_key'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$device = \IPS\Member\Device::loadAndAuthenticate( \IPS\Request::i()->cookie['device_key'], \IPS\Member::loggedIn() );
|
||||
$device->login_key = NULL;
|
||||
$device->save();
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
|
||||
/* Clear cookies */
|
||||
\IPS\Request::i()->clearLoginCookies();
|
||||
|
||||
/* Destroy the session (we have to explicitly reset the session cookie, see http://php.net/manual/en/function.session-destroy.php) */
|
||||
$_SESSION = array();
|
||||
$params = session_get_cookie_params();
|
||||
setcookie( session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] );
|
||||
session_destroy();
|
||||
session_start();
|
||||
|
||||
/* Member sync callback */
|
||||
\IPS\Member::loggedIn()->memberSync( 'onLogout', array( $redirectUrl ?: \IPS\Http\Url::internal('') ) );
|
||||
}
|
||||
|
||||
/* !Backwards Compatibility for login handlers created before 4.3 */
|
||||
|
||||
/**
|
||||
@@ -540,7 +636,7 @@ class _Login
|
||||
$method = \IPS\Login\Handler::constructFromData( $method );
|
||||
if ( $method instanceof \IPS\Login\LoginAbstract )
|
||||
{
|
||||
$return[ mb_substr( get_class( $method ), 10 ) ] = $method;
|
||||
$return[ mb_substr( \get_class( $method ), 10 ) ] = $method;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
|
||||
Reference in new issue
Block a user