Version 4.0.13.1
This commit is contained in:
1 parent
f9e857d255
commit
c6a86f0d9b
7517 files changed
+597400
-668041
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Login Exception Class
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 26 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login Exception Class
|
||||
*/
|
||||
class _Exception extends \DomainException
|
||||
{
|
||||
const INTERNAL_ERROR = 1;
|
||||
const REGISTRATION_DENIED_BY_SPAM_SERVICE = 2;
|
||||
const REGISTRATION_DISABLED = 3;
|
||||
const BAD_PASSWORD = 4;
|
||||
const NO_ACCOUNT = 5;
|
||||
const MERGE_SOCIAL_ACCOUNT = 6;
|
||||
|
||||
/**
|
||||
* @brief Member
|
||||
*/
|
||||
public $member = NULL;
|
||||
|
||||
/**
|
||||
* @bried Handler
|
||||
*/
|
||||
public $handler = NULL;
|
||||
|
||||
/**
|
||||
* @brief Details
|
||||
*/
|
||||
public $details = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $message Message
|
||||
* @param int $code Code
|
||||
* @param \Exception|NULL $previous Previous Exception
|
||||
* @param \IPS\Member|null $member Member
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $message, $code=NULL, $previous=NULL, $member=NULL )
|
||||
{
|
||||
if ( $code === static::BAD_PASSWORD and $member !== NULL )
|
||||
{
|
||||
$failedLogins = $member->failed_logins;
|
||||
$failedLogins[ \IPS\Request::i()->ipAddress() ][] = time();
|
||||
$member->failed_logins = $failedLogins;
|
||||
$member->save();
|
||||
}
|
||||
|
||||
parent::__construct( $message, $code, $previous );
|
||||
$this->member = $member;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief External Database Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* External Database Login Handler
|
||||
*/
|
||||
class _External extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Authentication types
|
||||
*/
|
||||
public $authTypes;
|
||||
|
||||
/**
|
||||
* Initiate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->authTypes = $this->settings['auth_types'] ?: \IPS\Login::AUTH_TYPE_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param array $values Values from from
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $values )
|
||||
{
|
||||
/* Build where clause */
|
||||
switch ( $this->authTypes )
|
||||
{
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME:
|
||||
$where = array( "{$this->settings['db_col_user']}=?", $values['auth'] );
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
$where = array( "{$this->settings['db_col_email']}=?", $values['auth'] );
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
$where = array( "{$this->settings['db_col_user']}=? OR {$this->settings['db_col_email']}=?", $values['auth'], $values['auth'] );
|
||||
break;
|
||||
|
||||
}
|
||||
if ( $this->settings['db_extra'] )
|
||||
{
|
||||
$where[0] .= $this->settings['db_extra'];
|
||||
}
|
||||
|
||||
/* Fetch result */
|
||||
try
|
||||
{
|
||||
$result = $this->externalDb()->select( '*', $this->settings['db_table'], $where )->first();
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
switch ( $this->authTypes )
|
||||
{
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
$type = 'username_or_email';
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME:
|
||||
$type = 'username';
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
$type = 'email_address';
|
||||
break;
|
||||
}
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $type ) ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Get a local account if one exists */
|
||||
$member = NULL;
|
||||
if ( $this->settings['db_col_user'] )
|
||||
{
|
||||
$_member = \IPS\Member::load( $result[ $this->settings['db_col_user'] ], 'name' );
|
||||
if ( $_member->member_id )
|
||||
{
|
||||
$member = $_member;
|
||||
}
|
||||
}
|
||||
if ( $this->settings['db_col_email'] )
|
||||
{
|
||||
$_member = \IPS\Member::load( $result[ $this->settings['db_col_email'] ], 'email' );
|
||||
if ( $_member->member_id )
|
||||
{
|
||||
$member = $_member;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the password doesn't match, throw an exception */
|
||||
if ( !\IPS\Login::compareHashes( $this->encryptedPassword( $values['password'] ), $result[ $this->settings['db_col_pass'] ] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
|
||||
/* Or, create a member */
|
||||
if ( $member === NULL )
|
||||
{
|
||||
$member = $this->createOrUpdateAccount( NULL, array(), $this->settings['db_col_user'] ? $result[ $this->settings['db_col_user'] ] : NULL, $this->settings['db_col_email'] ? $result[ $this->settings['db_col_email'] ] : NULL );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt Password
|
||||
*
|
||||
* @param string $password The password
|
||||
* @return bool
|
||||
*/
|
||||
protected function encryptedPassword( $password )
|
||||
{
|
||||
switch ( $this->settings['db_encryption'] )
|
||||
{
|
||||
case 'md5':
|
||||
return md5( $password );
|
||||
|
||||
case 'sha1':
|
||||
return sha1( $password );
|
||||
|
||||
default:
|
||||
return $password;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
return array(
|
||||
'login_external_conn',
|
||||
'sql_host' => new \IPS\Helpers\Form\Text( 'login_external_host', $this->settings['sql_host'] ?: 'localhost', TRUE ),
|
||||
'sql_user' => new \IPS\Helpers\Form\Text( 'login_external_user', $this->settings['sql_user'], TRUE ),
|
||||
'sql_pass' => new \IPS\Helpers\Form\Text( 'login_external_pass', $this->settings['sql_pass'], TRUE ),
|
||||
'sql_database' => new \IPS\Helpers\Form\Text( 'login_external_database', $this->settings['sql_database'], TRUE ),
|
||||
'sql_port' => new \IPS\Helpers\Form\Number( 'login_external_port', $this->settings['sql_port'], FALSE ),
|
||||
'sql_socket' => new \IPS\Helpers\Form\Text( 'login_external_socket', $this->settings['sql_socket'], FALSE ),
|
||||
'login_external_schema',
|
||||
'db_table' => new \IPS\Helpers\Form\Text( 'login_external_table', $this->settings['db_table'], TRUE ),
|
||||
'db_col_user' => new \IPS\Helpers\Form\Text( 'login_external_username', $this->settings['db_col_user'], FALSE, array(), function( $val )
|
||||
{
|
||||
if ( !$val and \IPS\Request::i()->login_auth_types & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
throw new \DomainException('login_external_username_err');
|
||||
}
|
||||
} ),
|
||||
'db_col_email' => new \IPS\Helpers\Form\Text( 'login_external_email', $this->settings['db_col_email'], FALSE, array(), function( $val )
|
||||
{
|
||||
if ( !$val and \IPS\Request::i()->login_auth_types & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
throw new \DomainException('login_external_email_err');
|
||||
}
|
||||
} ),
|
||||
'db_col_pass' => new \IPS\Helpers\Form\Text( 'login_external_password', $this->settings['db_col_pass'], TRUE ),
|
||||
'db_encryption' => new \IPS\Helpers\Form\Select( 'login_external_encryption', $this->settings['db_encryption'], TRUE, array( 'options' => array(
|
||||
'md5' => 'MD5',
|
||||
'sha1' => 'SHA1',
|
||||
'plaintext' => 'login_external_encryption_plain',
|
||||
) ) ),
|
||||
'db_extra' => new \IPS\Helpers\Form\Text( 'login_external_extra', isset( $this->settings['db_extra'] ) ? $this->settings['db_extra'] : '' ),
|
||||
'login_settings',
|
||||
'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', $this->settings['auth_types'], TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
) ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
$select = array( $this->settings['db_col_pass'] );
|
||||
|
||||
if ( $this->settings['db_col_user'] )
|
||||
{
|
||||
$select[] = $this->settings['db_col_user'];
|
||||
}
|
||||
|
||||
if ( $this->settings['db_col_email'] )
|
||||
{
|
||||
$select[] = $this->settings['db_col_email'];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$result = $this->externalDb()->select( implode( ',', $select ), $this->settings['db_table'], $this->settings['db_extra'] )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
// It's possible that no users exist, which is fine
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DB Connection
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
protected function externalDb()
|
||||
{
|
||||
return \IPS\Db::i( 'external_login', $this->settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME and $member->name and $this->usernameIsInUse( $member->name ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL and $member->email and $this->emailIsInUse( $member->email ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'username' or 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return $this->canProcess( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Email is in use?
|
||||
* Used when registering or changing an email address to check the new one is available
|
||||
*
|
||||
* @param string $email Email Address
|
||||
* @param \IPS\Member|NULL $exclude Member to exclude
|
||||
* @return bool|NULL Boolean indicates if email is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function emailIsInUse( $email, \IPS\Member $exclude=NULL )
|
||||
{
|
||||
if ( $exclude )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->externalDb()->select( $this->settings['db_col_email'], $this->settings['db_table'], array( "{$this->settings['db_col_email']}=?", $email ) )->first();
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Username is in use?
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @return bool|NULL Boolean indicates if username is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function usernameIsInUse( $username )
|
||||
{
|
||||
try
|
||||
{
|
||||
$result = $this->externalDb()->select( $this->settings['db_col_user'], $this->settings['db_table'], array( "{$this->settings['db_col_user']}=?", $username ) )->first();
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldEmail Old Email Address
|
||||
* @param string $newEmail New Email Address
|
||||
* @return void
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail )
|
||||
{
|
||||
if ( $this->settings['db_col_email'] )
|
||||
{
|
||||
$this->externalDb()->update( $this->settings['db_table'], array( $this->settings['db_col_email'] => $newEmail ), array( $this->settings['db_col_email'] . '=?', $oldEmail ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $newPassword New Password
|
||||
* @return void
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
{
|
||||
$where = '1=0';
|
||||
switch ( $this->authTypes )
|
||||
{
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME:
|
||||
$where = array( "{$this->settings['db_col_user']}=?", $member->name );
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
$where = array( "{$this->settings['db_col_email']}=?", $member->email );
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
|
||||
$this->externalDb()->update( $this->settings['db_table'], array( $this->settings['db_col_pass'] => $this->encryptedPassword( $newPassword ) ), $where );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
* @throws \IPS\Db\Exception
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
{
|
||||
if ( $this->settings['db_col_user'] )
|
||||
{
|
||||
$this->externalDb()->update( $this->settings['db_table'], array( $this->settings['db_col_user'] => $newUsername ), array( $this->settings['db_col_user'] . '=?', $oldUsername ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Facebook Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Facebook Login Handler
|
||||
*/
|
||||
class _Facebook extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'facebook-square';
|
||||
|
||||
/**
|
||||
* Get Form
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL for the login page
|
||||
* @return string
|
||||
*/
|
||||
public function loginForm( $url, $ucp=FALSE )
|
||||
{
|
||||
$url = \IPS\Http\Url::internal( 'applications/core/interface/facebook/auth.php', 'none' );
|
||||
|
||||
if ( $ucp )
|
||||
{
|
||||
$state = "ucp-" . \IPS\Session::i()->csrfKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
$state = \IPS\Dispatcher::i()->controllerLocation . "-" . \IPS\Session::i()->csrfKey;
|
||||
}
|
||||
|
||||
$scope = 'email';
|
||||
|
||||
if ( \IPS\Settings::i()->profile_comments )
|
||||
{
|
||||
$scope .= ',user_status,publish_actions';
|
||||
}
|
||||
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->facebook( "https://www.facebook.com/dialog/oauth?client_id={$this->settings['app_id']}&scope={$scope}&redirect_uri=".urlencode( $url ) . "&state={$state}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $url, $member=NULL )
|
||||
{
|
||||
$url = $url->setQueryString( 'loginProcess', 'facebook' );
|
||||
|
||||
try
|
||||
{
|
||||
/* CSRF Check */
|
||||
if ( \IPS\Request::i()->state !== \IPS\Session::i()->csrfKey )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'CSRF_FAIL', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Get a token */
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/oauth/access_token" )->request()->post( array(
|
||||
'client_id' => $this->settings['app_id'],
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/facebook/auth.php', 'none' ),
|
||||
'client_secret' => $this->settings['app_secret'],
|
||||
'code' => \IPS\Request::i()->code
|
||||
) )->decodeQueryString('access_token');
|
||||
}
|
||||
catch( \RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Now exchange it for a one that will last a bit longer in case the user wants to use syncing */
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.facebook.com/oauth/access_token" )->request()->post( array(
|
||||
'grant_type' => 'fb_exchange_token',
|
||||
'client_id' => $this->settings['app_id'],
|
||||
'client_secret' => $this->settings['app_secret'],
|
||||
'fb_exchange_token' => $response['access_token']
|
||||
) )->decodeQueryString('access_token');
|
||||
}
|
||||
catch( \RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Set up appsecret_proof https://developers.facebook.com/docs/graph-api/securing-requests */
|
||||
$appSecretProof = hash_hmac( 'sha256', $response['access_token'], $this->settings['app_secret'] );
|
||||
|
||||
/* Get the user data */
|
||||
$userData = \IPS\Http\Url::external( "https://graph.facebook.com/me?access_token={$response['access_token']}&appsecret_proof={$appSecretProof}" )->request()->get()->decodeJson();
|
||||
|
||||
/* Find or create member */
|
||||
$member = $this->createOrUpdateAccount( $member ?: \IPS\Member::load( $userData['id'], 'fb_uid' ), array(
|
||||
'fb_uid' => $userData['id'],
|
||||
'fb_token' => $response['access_token']
|
||||
), $this->settings['real_name'] ? $userData['name'] : NULL, ( isset( $userData['email'] ) AND $userData['email'] ) ? $userData['email'] : NULL, $response['access_token'], array( 'photo' => TRUE, 'cover' => TRUE, 'status' => '' ) );
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param mixed $details Details as they were passed to the exception thrown in authenticate()
|
||||
* @return void
|
||||
*/
|
||||
public static function link( \IPS\Member $member, $details )
|
||||
{
|
||||
$userData = \IPS\Http\Url::external( "https://graph.facebook.com/me?access_token={$details}" )->request()->get()->decodeJson();
|
||||
$member->fb_uid = $userData['id'];
|
||||
$member->fb_token = $details;
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
\IPS\Output::i()->sidebar['actions'] = array(
|
||||
'help' => array(
|
||||
'title' => 'help',
|
||||
'icon' => 'question-circle',
|
||||
'link' => \IPS\Http\Url::ips( 'docs/login_facebook' ),
|
||||
'target' => '_blank',
|
||||
'class' => ''
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'app_id' => new \IPS\Helpers\Form\Text( 'login_facebook_app', ( isset( $this->settings['app_id'] ) ) ? $this->settings['app_id'] : '', TRUE ),
|
||||
'app_secret' => new \IPS\Helpers\Form\Text( 'login_facebook_secret', ( isset( $this->settings['app_secret'] ) ) ? $this->settings['app_secret'] : '', TRUE ),
|
||||
'real_name' => new \IPS\Helpers\Form\YesNo( 'login_real_name', ( isset( $this->settings['real_name'] ) ) ? $this->settings['real_name'] : FALSE, TRUE )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
return ( $member->fb_uid and $member->fb_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Google Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 20 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Login Handler
|
||||
*/
|
||||
class _Google extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'google-plus';
|
||||
|
||||
/**
|
||||
* Get Form
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param bool $ucp Is UCP? (as opposed to login form)
|
||||
* @return string
|
||||
*/
|
||||
public function loginForm( $url, $ucp=FALSE )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->google( (string) $this->_googleSignInUrl( ( $ucp ? 'ucp' : \IPS\Dispatcher::i()->controllerLocation ), $ucp ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sign in URL
|
||||
*
|
||||
* @param string $base Controls where the user is taken back to
|
||||
* @param bool $forcePrompt Force prompt? Will mean user is shown the Google site even if previously authenticated. Only to be used if a new refresh token is needed.
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
protected function _googleSignInUrl( $base, $forcePrompt )
|
||||
{
|
||||
$params = array(
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->settings['client_id'],
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' ),
|
||||
'scope' => 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
|
||||
'access_type' => 'offline',
|
||||
'state' => $base . '-' . \IPS\Session::i()->csrfKey
|
||||
);
|
||||
|
||||
if ( $forcePrompt )
|
||||
{
|
||||
$params['approval_prompt'] = 'force';
|
||||
}
|
||||
|
||||
return \IPS\Http\Url::external( "https://accounts.google.com/o/oauth2/auth" )->setQueryString( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $url, $member=NULL )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* CSRF Check */
|
||||
if ( \IPS\Request::i()->state !== \IPS\Session::i()->csrfKey )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'CSRF_FAIL', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Get access token so we can get user data */
|
||||
$response = \IPS\Http\Url::external( "https://accounts.google.com/o/oauth2/token" )->request()->post( array(
|
||||
'code' => \IPS\Request::i()->code,
|
||||
'client_id' => $this->settings['client_id'],
|
||||
'client_secret' => $this->settings['client_secret'],
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' ),
|
||||
'grant_type' => 'authorization_code',
|
||||
) )->decodeJson();
|
||||
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Get user data */
|
||||
$userData = \IPS\Http\Url::external( "https://www.googleapis.com/plus/v1/people/me?access_token={$response['access_token']}" )->request()->get()->decodeJson('id');
|
||||
$email = $userData = \IPS\Http\Url::external( "https://www.googleapis.com/oauth2/v1/userinfo?access_token={$response['access_token']}" )->request()->get()->decodeJson('email');
|
||||
|
||||
/* Set member properties */
|
||||
$memberProperties = array( 'google_id' => $userData['id'] );
|
||||
if ( isset( $response['refresh_token'] ) )
|
||||
{
|
||||
$memberProperties['google_token'] = $response['refresh_token'];
|
||||
}
|
||||
|
||||
/* Find or create member */
|
||||
$member = $this->createOrUpdateAccount( $member ?: \IPS\Member::load( $userData['id'], 'google_id' ), $memberProperties, $this->settings['real_name'] ? $userData['name'] : NULL, $userData['email'], $response['access_token'], array( 'photo' => TRUE, 'cover' => TRUE, 'status' => '' ) );
|
||||
|
||||
/* We need a refresh token so we can get their user data again later.
|
||||
This is only provided if they actually clicked through the Google auth screen
|
||||
and not if they were authenticated automatically. So if we don't have one,
|
||||
send them back, forcing the login screen, so we get one */
|
||||
if ( !$member->google_token )
|
||||
{
|
||||
\IPS\Output::i()->redirect( $this->_googleSignInUrl( \IPS\Request::i()->base, TRUE ) );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param mixed $details Details as they were passed to the exception thrown in authenticate()
|
||||
* @return void
|
||||
*/
|
||||
public static function link( \IPS\Member $member, $details )
|
||||
{
|
||||
$userData = \IPS\Http\Url::external( "https://www.googleapis.com/plus/v1/people/me?access_token={$details}" )->request()->get()->decodeJson('id');
|
||||
$member->google_id = $userData['id'];
|
||||
$member->google_token = $details;
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
\IPS\Output::i()->sidebar['actions'] = array(
|
||||
'help' => array(
|
||||
'title' => 'help',
|
||||
'icon' => 'question-circle',
|
||||
'link' => \IPS\Http\Url::ips( 'docs/login_google' ),
|
||||
'target' => '_blank',
|
||||
'class' => ''
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'client_id' => new \IPS\Helpers\Form\Text( 'login_google_id', ( isset( $this->settings['client_id'] ) ) ? $this->settings['client_id'] : '', TRUE ),
|
||||
'client_secret' => new \IPS\Helpers\Form\Text( 'login_google_secret', ( isset( $this->settings['client_secret'] ) ) ? $this->settings['client_secret'] : '', TRUE ),
|
||||
'real_name' => new \IPS\Helpers\Form\YesNo( 'login_real_name', ( isset( $this->settings['real_name'] ) ) ? $this->settings['real_name'] : FALSE, TRUE )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
* @throws \UnexpectedValueException If response code is not 302
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://accounts.google.com/o/oauth2/auth" )->setQueryString( array(
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->settings['client_id'],
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' ),
|
||||
'scope' => 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
|
||||
'state' => 'admin-' . \IPS\Session::i()->csrfKey
|
||||
) )->request()->get();
|
||||
|
||||
if ( $response->httpResponseCode != 200 )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Google') ) ) ) );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Google') ) ) ) );
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
return ( $member->google_id and $member->google_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Internal Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 13 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal Login Handler
|
||||
*/
|
||||
class _Internal extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Authentication types
|
||||
*/
|
||||
public $authTypes;
|
||||
|
||||
/**
|
||||
* Initiate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->authTypes = $this->settings['auth_types'] ?: \IPS\Login::AUTH_TYPE_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param array $values Values from from
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $values )
|
||||
{
|
||||
/* Get member(s) */
|
||||
$members = array();
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
$_member = \IPS\Member::load( $values['auth'], 'name', NULL );
|
||||
if ( $_member->member_id )
|
||||
{
|
||||
$members[] = $_member;
|
||||
}
|
||||
|
||||
$_legacyMember = \IPS\Member::load( \IPS\Request::legacyEscape( $values['auth'] ), 'name', NULL );
|
||||
if ( $_legacyMember->member_id )
|
||||
{
|
||||
$members[] = $_legacyMember;
|
||||
}
|
||||
}
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$_member = \IPS\Member::load( $values['auth'], 'email' );
|
||||
if ( $_member->member_id )
|
||||
{
|
||||
$members[] = $_member;
|
||||
}
|
||||
|
||||
$_legacyMember = \IPS\Member::load( \IPS\Request::legacyEscape( $values['auth'] ), 'email' );
|
||||
if ( $_legacyMember->member_id )
|
||||
{
|
||||
$members[] = $_legacyMember;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we didn't match any, throw an exception */
|
||||
if ( empty( $members ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $this->getLoginType( $this->authTypes ) ) ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Check the password for each possible account */
|
||||
foreach ( $members as $member )
|
||||
{
|
||||
if ( \IPS\Login::compareHashes( $member->members_pass_hash, $member->encryptedPassword( $values['password'] ) ) )
|
||||
{
|
||||
/* If it's the old style, convert it to the new */
|
||||
if ( mb_strlen( $member->members_pass_salt ) !== 22 )
|
||||
{
|
||||
$member->members_pass_salt = $member->generateSalt();
|
||||
$member->members_pass_hash = $member->encryptedPassword( $values['password'] );
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
}
|
||||
|
||||
/* Still here? Throw a password incorrect exception */
|
||||
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
return array(
|
||||
'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', $this->settings['auth_types'], TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
) ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get whether or not this node is enabled
|
||||
*
|
||||
* @note Return value NULL indicates the node cannot be enabled/disabled
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function get__enabled()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get whether or not this node is locked to current enabled/disabled status
|
||||
*
|
||||
* @note Return value NULL indicates the node cannot be enabled/disabled
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function get__locked()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
if ( !$member->members_pass_hash )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME and $member->name )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL and $member->email )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Does the currently logged in user have permission to delete this node?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canDelete()
|
||||
{
|
||||
return FALSE; # this is bad, mkay
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'username' or 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
/* We can only change the internal password if we haave one (i.e. we didn't sign in with a social service) because we need the existing password */
|
||||
if ( $type === 'password' )
|
||||
{
|
||||
return (bool) $member->members_pass_hash;
|
||||
}
|
||||
|
||||
/* But we can always change username and email */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email is in use?
|
||||
* Used when registering or changing an email address to check the new one is available
|
||||
*
|
||||
* @param string $email Email Address
|
||||
* @param \IPS\Member|NULL $exclude Member to exclude
|
||||
* @return bool|NULL Boolean indicates if email is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function emailIsInUse( $email, \IPS\Member $exclude=NULL )
|
||||
{
|
||||
$member = \IPS\Member::load( $email, 'email' );
|
||||
return (bool) ( $member->member_id and $member != $exclude );
|
||||
}
|
||||
|
||||
/**
|
||||
* Username is in use?
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @return bool|NULL Boolean indicates if username is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function usernameIsInUse( $username )
|
||||
{
|
||||
$member = \IPS\Member::load( $username, 'name' );
|
||||
return (bool) $member->member_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldEmail Old Email Address
|
||||
* @param string $newEmail New Email Address
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail )
|
||||
{
|
||||
$member->email = $newEmail;
|
||||
$member->save();
|
||||
$member->memberSync( 'onEmailChange', array( $newEmail, $oldEmail ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $newPassword New Password
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
{
|
||||
$member->members_pass_salt = $member->generateSalt();
|
||||
$member->members_pass_hash = $member->encryptedPassword( $newPassword );
|
||||
$member->save();
|
||||
$member->memberSync( 'onPassChange', array( $newPassword ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
{
|
||||
$member->name = $newUsername;
|
||||
$member->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief IPS Connect Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* IPS Connect Login Handler
|
||||
*/
|
||||
class _Ipsconnect extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Authentication types
|
||||
*/
|
||||
public $authTypes;
|
||||
|
||||
/**
|
||||
* Initiate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->authTypes = ( !empty( $this->settings['auth_types'] ) ) ? $this->settings['auth_types'] : \IPS\Login::AUTH_TYPE_USERNAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief We are coming from a login request, so do not attempt to register us on the master if a local account is created
|
||||
*/
|
||||
protected $skipMasterRegistration = FALSE;
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param array $values Values from form
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $values )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Are we being returned from a cross-domain login? */
|
||||
if( \IPS\Request::i()->loginProcess == 'ipsconnect' AND \IPS\Request::i()->id AND \IPS\Request::i()->key )
|
||||
{
|
||||
$member = \IPS\Member::load( \IPS\Request::i()->id, 'ipsconnect_id' );
|
||||
|
||||
if( $member->member_id AND \IPS\Login::compareHashes( \IPS\Request::i()->key, md5( $this->settings['key'] . $member->ipsconnect_id ) ) )
|
||||
{
|
||||
return $member;
|
||||
}
|
||||
}
|
||||
|
||||
/* First, we need to fetch the user's salt to prevent sending password in plaintext */
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'fetchSalt', 'key' => md5( $this->settings['key'] . $values['auth'] ), 'idType' => $this->authTypes, 'id' => $values['auth'] ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
/* No response or status is not 'SUCCESS' */
|
||||
if ( empty( $response ) OR empty( $response['status'] ) OR $response['status'] != 'SUCCESS' OR !$response['pass_salt'] )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Now create a dummy user to get password hash */
|
||||
$testUser = new \IPS\Member;
|
||||
$testUser->members_pass_salt = $response['pass_salt'];
|
||||
$password = $testUser->encryptedPassword( $values['password'] );
|
||||
|
||||
/* Now try to login */
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'login', 'key' => md5( $this->settings['key'] . $values['auth'] ), 'idType' => $this->authTypes, 'id' => $values['auth'], 'password' => $password ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
/* Are we validating? */
|
||||
if( $response['connect_status'] == 'VALIDATING' )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_remote_validate', FALSE, array( 'sprintf' => array( $response['connect_revalidate_url'] ) ) ) );
|
||||
}
|
||||
|
||||
/* No response or status is not 'SUCCESS' */
|
||||
if ( empty( $response ) OR empty( $response['status'] ) OR $response['status'] != 'SUCCESS' )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* No connect status means account not found */
|
||||
if( empty( $response['connect_status'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $this->getLoginType( $this->authTypes ) ) ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Load the local account now, or create it if it does not exist */
|
||||
$member = \IPS\Member::load( $response['connect_id'], 'ipsconnect_id' );
|
||||
if( !$member->member_id )
|
||||
{
|
||||
$member = \IPS\Member::load( $response['email'], 'email' );
|
||||
}
|
||||
|
||||
/* Update any changes on existing accounts */
|
||||
if ( $member->member_id )
|
||||
{
|
||||
if( $response['connect_id'] != $member->ipsconnect_id )
|
||||
{
|
||||
$member->ipsconnect_id = $response['connect_id'];
|
||||
}
|
||||
|
||||
if( $response['email'] != $member->email )
|
||||
{
|
||||
$member->email = $response['email'];
|
||||
}
|
||||
|
||||
if( $response['name'] != $member->name AND \IPS\CONNECT_NOSYNC_NAMES === FALSE )
|
||||
{
|
||||
$member->name = $response['name'];
|
||||
}
|
||||
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/* Otherwise create the account if it does not exist yet */
|
||||
else
|
||||
{
|
||||
$this->skipMasterRegistration = TRUE;
|
||||
|
||||
$member = $this->createOrUpdateAccount( $member, array(
|
||||
'ipsconnect_id' => $response['connect_id'],
|
||||
'members_pass_salt' => $testUser->members_pass_salt,
|
||||
'members_pass_hash' => $password
|
||||
), $response['name'] ?: NULL, $response['email'] ?: NULL );
|
||||
|
||||
$this->skipMasterRegistration = FALSE;
|
||||
}
|
||||
|
||||
/* Do we have any custom callbacks we need to call? */
|
||||
$this->connectSuccessful( $member );
|
||||
|
||||
/* If we are still here and successful, redirect to master to log us in to each installation */
|
||||
$url = \IPS\Http\Url::internal( '' );
|
||||
\IPS\Request::i()->ref = (string) \IPS\Http\Url::external( $this->settings['url'] )->setQueryString(
|
||||
array(
|
||||
'do' => 'crossLogin',
|
||||
'key' => md5( $this->settings['key'] . $member->ipsconnect_id ),
|
||||
'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php',
|
||||
'id' => $member->ipsconnect_id,
|
||||
'returnTo' => (string) $url->setQueryString(
|
||||
array(
|
||||
'loginProcess' => 'ipsconnect',
|
||||
'key' => md5( $this->settings['key'] . $member->ipsconnect_id ),
|
||||
'id' => $member->ipsconnect_id,
|
||||
'ref' => (string) \IPS\Login::getDestination()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $member;
|
||||
}
|
||||
/* Have to catch \RuntimeException to catch the BAD_JSON throw from Response.php */
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* If we are still here, throw a generic bad password exception */
|
||||
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method called upon a successful login - useful for plugins to do "something" when login is successful
|
||||
*
|
||||
* @param \IPS\Member $member Member that is logged in
|
||||
* @return void
|
||||
* @note By default this method does nothing, but is abstracted to allow easy extending
|
||||
*/
|
||||
public function connectSuccessful( \IPS\Member $member )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the URL to redirect to following registration, if any
|
||||
*
|
||||
* @param \IPS\Member $member The member that just registered
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
public function getRegistrationDestination( $member )
|
||||
{
|
||||
return \IPS\Http\Url::external( $this->settings['url'] )->setQueryString( array( 'do' => 'crossLogin', 'key' => md5( $this->settings['key'] . $member->ipsconnect_id ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'id' => $member->ipsconnect_id, 'returnTo' => (string) \IPS\Http\Url::internal( '', 'front' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
return array(
|
||||
'url' => new \IPS\Helpers\Form\Text( 'login_ipsconnect_url', $this->settings['url'], TRUE ),
|
||||
'key' => new \IPS\Helpers\Form\Text( 'login_ipsconnect_key', $this->settings['key'], TRUE ),
|
||||
'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', isset( $this->settings['auth_types'] ) ? $this->settings['auth_types'] : \IPS\Login::AUTH_TYPE_USERNAME, TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
) ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
return (bool) $this->_getConnectId( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'username' or 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return (bool) $this->_getConnectId( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
if( !$this->settings['url'] or $this->settings['url'] == (string) \IPS\Http\Url::internal( 'applications/core/interface/ipsconnect/ipsconnect.php', 'none' ) )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_error_ipsconnect_self') );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'verifySettings', 'key' => $this->settings['key'], 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'ourKey' => md5( md5( \IPS\Settings::i()->sql_user . \IPS\Settings::i()->sql_pass ) . \IPS\Settings::i()->board_start ) ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
if ( empty( $response ) OR empty( $response['status'] ) OR $response['status'] != 'SUCCESS' )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Ipsconnect') ) ) ) );
|
||||
}
|
||||
}
|
||||
/* Have to catch \RuntimeException to catch the BAD_JSON throw from Response.php */
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Ipsconnect') ) ) ) );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the user's associated IPS Connect ID
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return int|NULL The IPS Connect ID or NULL if none found
|
||||
*/
|
||||
protected function _getConnectId( \IPS\Member $member )
|
||||
{
|
||||
/* Already have it? */
|
||||
if( $member->ipsconnect_id )
|
||||
{
|
||||
return $member->ipsconnect_id;
|
||||
}
|
||||
|
||||
/* An incomplete member may not have an email address, which won't return an ID */
|
||||
if( !$member->email )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Fetch it from the master */
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'fetchId', 'key' => md5( $this->settings['key'] . $member->email ), 'id' => $member->email, 'idType' => 'email' ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
if ( !empty( $response ) AND !empty( $response['status'] ) AND $response['status'] == 'SUCCESS' AND $response['connect_id'] )
|
||||
{
|
||||
$member->ipsconnect_id = $response['connect_id'];
|
||||
$member->save();
|
||||
}
|
||||
}
|
||||
/* Have to catch \RuntimeException to catch the BAD_JSON throw from Response.php */
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email is in use?
|
||||
* Used when registering or changing an email address to check the new one is available
|
||||
*
|
||||
* @param string $email Email Address
|
||||
* @param \IPS\Member|NULL $exclude Member to exclude
|
||||
* @return bool|NULL Boolean indicates if email is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function emailIsInUse( $email, \IPS\Member $exclude=NULL )
|
||||
{
|
||||
if( $exclude !== NULL AND $exclude instanceof \IPS\Member AND $email == $exclude->email )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'checkEmail', 'key' => $this->settings['key'], 'email' => $email ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
if ( !empty( $response ) AND !empty( $response['status'] ) AND $response['status'] == 'SUCCESS' AND $response['used'] != 0 )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
/* Have to catch \RuntimeException to catch the BAD_JSON throw from Response.php */
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Username is in use?
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @return bool|NULL Boolean indicates if username is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function usernameIsInUse( $username )
|
||||
{
|
||||
if( \IPS\CONNECT_NOSYNC_NAMES === TRUE )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'checkName', 'key' => $this->settings['key'], 'name' => $username ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
if ( !empty( $response ) AND !empty( $response['status'] ) AND $response['status'] == 'SUCCESS' AND $response['used'] != 0 )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
/* Have to catch \RuntimeException to catch the BAD_JSON throw from Response.php */
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an account off
|
||||
*
|
||||
* @param \IPS\Member $member The member that was just logged out
|
||||
* @param \IPS\Http\Url $redirectUrl The URL to send the user back to
|
||||
* @return void
|
||||
* @note This is NOT called if you force log out all users from the ACP on an individual site
|
||||
*/
|
||||
public function logoutAccount( \IPS\Member $member, \IPS\Http\Url $redirectUrl )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if ( $connectId )
|
||||
{
|
||||
\IPS\Output::i()->redirect(
|
||||
\IPS\Http\Url::external( $this->settings['url'] )->setQueryString( array(
|
||||
'do' => 'logout',
|
||||
'key' => md5( $this->settings['key'] . $connectId ),
|
||||
'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php',
|
||||
'id' => $connectId,
|
||||
'returnTo' => (string) $redirectUrl,
|
||||
) )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldEmail Old Email Address
|
||||
* @param string $newEmail New Email Address
|
||||
* @return void
|
||||
*/
|
||||
public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if( $connectId )
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'changeEmail', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'email' => $newEmail, 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
catch( \RuntimeException $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $newPassword New Password
|
||||
* @return void
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if( $connectId )
|
||||
{
|
||||
/* Now create a dummy user to get password hash */
|
||||
$testUser = new \IPS\Member;
|
||||
$testUser->members_pass_salt = $testUser->generateSalt();
|
||||
$password = $testUser->encryptedPassword( $newPassword );
|
||||
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'changePassword', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'pass_hash' => $password, 'pass_salt' => $testUser->members_pass_salt, 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
catch( \RuntimeException $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
{
|
||||
if( \IPS\CONNECT_NOSYNC_NAMES === TRUE )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if( $connectId )
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'changeName', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'name' => $newUsername, 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
catch( \RuntimeException $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account
|
||||
*
|
||||
* @param \IPS\Member $member The member that was just created
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function createAccount( \IPS\Member $member )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* If account creation is occurring because the user just successfully logged in, do not try to then create an account on the master install */
|
||||
if( $this->skipMasterRegistration === TRUE )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array(
|
||||
'do' => 'register',
|
||||
'key' => $this->settings['key'],
|
||||
'name' => $member->name,
|
||||
'email' => $member->email,
|
||||
'pass_hash' => $member->members_pass_hash,
|
||||
'pass_salt' => $member->members_pass_salt,
|
||||
'revalidateUrl' => ( $member->members_bitoptions['validating'] == TRUE ) ? (string) \IPS\Http\Url::internal( "", 'front' ) : '',
|
||||
'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php'
|
||||
) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
if( !empty( $response ) AND !empty( $response['status'] ) AND $response['status'] == 'SUCCESS' AND !empty( $response['connect_id'] ) )
|
||||
{
|
||||
$member->ipsconnect_id = $response['connect_id'];
|
||||
$member->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function validateAccount( \IPS\Member $member )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if( $connectId )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'validate', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function deleteAccount( \IPS\Member $member )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if( $connectId )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'delete', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban or unban account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param bool TRUE means member is being banned, FALSE means they are being unbanned
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function banAccount( \IPS\Member $member, $ban=TRUE )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
|
||||
if( $connectId )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'ban', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'status' => (int) $ban, 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two accounts
|
||||
*
|
||||
* @param \IPS\Member $member The member to keep with original data
|
||||
* @param \IPS\Member $member2 The member that will be deleted
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function mergeAccounts( \IPS\Member $member, \IPS\Member $member2 )
|
||||
{
|
||||
if( \IPS\Request::i()->slaveCall )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$connectId = $this->_getConnectId( $member );
|
||||
$connectId2 = $this->_getConnectId( $member2 );
|
||||
|
||||
if( $connectId AND $connectId2 )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] )
|
||||
->setQueryString( array( 'do' => 'merge', 'key' => md5( $this->settings['key'] . $connectId ), 'url' => \IPS\Settings::i()->base_url . '/applications/core/interface/ipsconnect/ipsconnect.php', 'remove' => $connectId2, 'id' => $connectId ) )
|
||||
->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief LDAP Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP Login Handler
|
||||
*/
|
||||
class _Ldap extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Authentication types
|
||||
*/
|
||||
public $authTypes = 0;
|
||||
|
||||
/**
|
||||
* @brief LDAP Resource
|
||||
*/
|
||||
protected $ldap;
|
||||
|
||||
/**
|
||||
* Initiate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if ( $this->settings['uid_field'] )
|
||||
{
|
||||
$this->authTypes += \IPS\Login::AUTH_TYPE_USERNAME;
|
||||
}
|
||||
if ( $this->settings['email_field'] )
|
||||
{
|
||||
$this->authTypes += \IPS\Login::AUTH_TYPE_EMAIL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param array $values Values from from
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $values )
|
||||
{
|
||||
/* Get user */
|
||||
$result = $this->getUser( $values['auth'], $values['auth'] );
|
||||
if ( !$result )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('username') ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Find or create member */
|
||||
$member = \IPS\Member::load( $values['auth'], $result['type'] );
|
||||
|
||||
/* Check Password */
|
||||
if ( !@ldap_bind( $this->ldap, ldap_get_dn( $this->ldap, $result['resource'] ), ( $this->settings['pw_required'] ? $values['password'] : '' ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
|
||||
/* Return or create member */
|
||||
if ( !$member->member_id )
|
||||
{
|
||||
$userData = ldap_get_attributes( $this->ldap, $result['resource'] );
|
||||
|
||||
$member = $this->createOrUpdateAccount(
|
||||
NULL,
|
||||
array(),
|
||||
( $this->settings['uid_field'] and isset( $userData[ $this->settings['uid_field'] ][0] ) ) ? $userData[ $this->settings['uid_field'] ][0] : NULL,
|
||||
( $this->settings['email_field'] and isset( $userData[ $this->settings['email_field'] ][0] ) ) ? $userData[ $this->settings['email_field'] ][0] : NULL
|
||||
);
|
||||
}
|
||||
return $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to LDAP server
|
||||
*
|
||||
* @return resource
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function ldap()
|
||||
{
|
||||
/* Connect to server */
|
||||
if ( $this->settings['server_port'] )
|
||||
{
|
||||
$this->ldap = ldap_connect( $this->settings['server_host'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->ldap = ldap_connect( $this->settings['server_host'], $this->settings['server_port'] );
|
||||
}
|
||||
|
||||
/* Specify Protocol Version */
|
||||
ldap_set_option( $this->ldap, LDAP_OPT_PROTOCOL_VERSION, $this->settings['server_protocol'] );
|
||||
|
||||
/* OPT Referrals */
|
||||
if ( $this->settings['opt_referrals'] )
|
||||
{
|
||||
ldap_set_option( $this->ldap, LDAP_OPT_REFERRALS, true );
|
||||
}
|
||||
else
|
||||
{
|
||||
ldap_set_option( $this->ldap, LDAP_OPT_REFERRALS, false );
|
||||
}
|
||||
|
||||
/* Bind to directory */
|
||||
if ( $this->settings['server_user'] or $this->settings['server_pass'] )
|
||||
{
|
||||
$bind = ldap_bind( $this->ldap, $this->settings['server_user'], $this->settings['server_pass'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$bind = ldap_bind( $this->ldap );
|
||||
}
|
||||
|
||||
if ( $bind === FALSE )
|
||||
{
|
||||
throw new \RuntimeException( 'ldap_err_bind' );
|
||||
}
|
||||
|
||||
return $this->ldap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user
|
||||
*
|
||||
* @param string $username The username
|
||||
* @paeam string $email The email address
|
||||
* @return array('resource' => resource, 'type' => 'name'|'email)|FALSE
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
protected function getUser( $username=NULL, $email=NULL )
|
||||
{
|
||||
$result = NULL;
|
||||
$type = NULL;
|
||||
|
||||
/* Connect */
|
||||
try
|
||||
{
|
||||
$this->ldap = $this->ldap();
|
||||
}
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Try email address */
|
||||
if ( $email and $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$result = $this->getUserWithFilter("{$this->settings['email_field']}={$email}");
|
||||
if ( $result )
|
||||
{
|
||||
$type = 'email';
|
||||
}
|
||||
}
|
||||
|
||||
/* Try username */
|
||||
if ( !$result and $username and $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
$result = $this->getUserWithFilter("{$this->settings['uid_field']}={$username}{$this->settings['un_suffix']}");
|
||||
if ( $result )
|
||||
{
|
||||
$type = 'name';
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $result ? array( 'resource' => $result, 'type' => $type ) : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user with filter
|
||||
*
|
||||
* @param string $filter Filter
|
||||
* @return resource|FALSE
|
||||
*/
|
||||
protected function getUserWithFilter( $filter )
|
||||
{
|
||||
/* Add any additional filter */
|
||||
if ( $this->settings['filter'] )
|
||||
{
|
||||
$filter = ( mb_substr( $this->settings['filter'], 0, 1 ) === '(' ) ? "(&({$filter}){$this->settings['filter']})" : "(&({$filter})({$this->settings['filter']}))";
|
||||
}
|
||||
|
||||
/* Get user */
|
||||
$search = ldap_search( $this->ldap, $this->settings['base_dn'], $filter );
|
||||
$result = ldap_first_entry( $this->ldap, $search );
|
||||
|
||||
/* Return */
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
return array(
|
||||
'server_protocol' => new \IPS\Helpers\Form\Select( 'ldap_server_protocol', $this->settings['server_protocol'], TRUE, array( 'options' => array( 3 => 3, 2 => 2 ) ) ),
|
||||
'server_host' => new \IPS\Helpers\Form\Text( 'ldap_server_host', $this->settings['server_host'], TRUE ),
|
||||
'server_port' => new \IPS\Helpers\Form\Number( 'ldap_server_port', $this->settings['server_port'] ),
|
||||
'opt_referrals' => new \IPS\Helpers\Form\YesNo( 'ldap_opt_referrals', $this->settings['opt_referrals'] ?: FALSE, TRUE ),
|
||||
'server_user' => new \IPS\Helpers\Form\Text( 'ldap_server_user', $this->settings['server_user'] ),
|
||||
'server_pass' => new \IPS\Helpers\Form\Text( 'ldap_server_pass', $this->settings['server_pass'] ),
|
||||
'base_dn' => new \IPS\Helpers\Form\Text( 'ldap_base_dn', $this->settings['base_dn'], TRUE ),
|
||||
'uid_field' => new \IPS\Helpers\Form\Text( 'ldap_uid_field', $this->settings['uid_field'] ?: 'uid' ),
|
||||
'email_field' => new \IPS\Helpers\Form\Text( 'ldap_email_field', $this->settings['email_field'] ?: 'mail' ),
|
||||
'un_suffix' => new \IPS\Helpers\Form\Text( 'ldap_un_suffix', $this->settings['un_suffix'] ),
|
||||
'pw_required' => new \IPS\Helpers\Form\YesNo( 'ldap_pw_required', $this->settings['pw_required'] ?: TRUE, TRUE ),
|
||||
'filter' => new \IPS\Helpers\Form\Text( 'ldap_filter', $this->settings['filter'] ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
if ( !extension_loaded('ldap') )
|
||||
{
|
||||
throw new \InvalidArgumentException( 'login_ldap_err' );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->ldap();
|
||||
}
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( 'login_ldap_err_connect' );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME and $member->name and $this->usernameIsInUse( $member->name ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_EMAIL and $member->email and $this->emailIsInUse( $member->email ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'username' or 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return $this->canProcess( $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Email is in use?
|
||||
* Used when registering or changing an email address to check the new one is available
|
||||
*
|
||||
* @param string $email Email Address
|
||||
* @param \IPS\Member|NULL $exclude Member to exclude
|
||||
* @return bool|NULL Boolean indicates if email is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function emailIsInUse( $email, \IPS\Member $exclude=NULL )
|
||||
{
|
||||
if ( $exclude )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return (bool) $this->getUser( NULL, $email );
|
||||
}
|
||||
catch ( \IPS\Login\Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Username is in use?
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @return bool|NULL Boolean indicates if username is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function usernameIsInUse( $username )
|
||||
{
|
||||
try
|
||||
{
|
||||
return (bool) $this->getUser( $username, NULL );
|
||||
}
|
||||
catch ( \IPS\Login\Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldEmail Old Email Address
|
||||
* @param string $newEmail New Email Address
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail )
|
||||
{
|
||||
$user = $this->getUser( NULL, $member->email );
|
||||
if ( $user )
|
||||
{
|
||||
ldap_modify( $this->ldap, ldap_get_dn( $this->ldap, $user['resource'] ), array( $this->settings['email_field'] => $newEmail ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $newPassword New Password
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
{
|
||||
$user = $this->getUser( $member->name, $member->email );
|
||||
if ( $user )
|
||||
{
|
||||
ldap_modify( $this->ldap, ldap_get_dn( $this->ldap, $user['resource'] ), array( 'userPassword' => "{SHA}" . base64_encode( pack( "H*", sha1( $newPassword ) ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
{
|
||||
$user = $this->getUser( $member->name, $member->email );
|
||||
if ( $user )
|
||||
{
|
||||
ldap_modify( $this->ldap, ldap_get_dn( $this->ldap, $user['resource'] ), array( $this->settings['uid_field'] => $newUsername . $this->settings['un_suffix'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief LinkedIn Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 20 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LinkedIn Login Handler
|
||||
*/
|
||||
class _Linkedin extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'linkedin';
|
||||
|
||||
/**
|
||||
* Get Form
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param bool $ucp Is UCP? (as opposed to login form)
|
||||
* @return string
|
||||
*/
|
||||
public function loginForm( $url, $ucp=FALSE )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->linkedin( (string) \IPS\Http\Url::external( "https://www.linkedin.com/uas/oauth2/authorization" )->setQueryString( array(
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->settings['api_key'],
|
||||
'scope' => 'r_basicprofile r_emailaddress',
|
||||
'state' => ( $ucp ? 'ucp' : \IPS\Dispatcher::i()->controllerLocation ),
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/linkedin/auth.php', 'none' ),
|
||||
) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $url, $member=NULL )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Get a token */
|
||||
$response = \IPS\Http\Url::external( "https://www.linkedin.com/uas/oauth2/accessToken?" . http_build_query( array(
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => \IPS\Request::i()->code,
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/linkedin/auth.php', 'none' ),
|
||||
'client_id' => $this->settings['api_key'],
|
||||
'client_secret' => $this->settings['secret_key'],
|
||||
) ) )->request()->post()->decodeJson();
|
||||
|
||||
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Get user data */
|
||||
$userData = array();
|
||||
foreach ( \IPS\Http\Url::external( "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address)?oauth2_access_token={$response['access_token']}" )->request()->get()->decodeXml() as $k => $v )
|
||||
{
|
||||
$userData[ $k ] = (string) $v;
|
||||
}
|
||||
|
||||
/* Find or create member */
|
||||
$member = $this->createOrUpdateAccount( $member ?: \IPS\Member::load( $userData['id'], 'linkedin_id' ), array(
|
||||
'linkedin_id' => $userData['id'],
|
||||
'linkedin_token' => $response['access_token'],
|
||||
), $this->settings['real_name'] ? $userData['formatted-name'] : NULL, $userData['email-address'], $response['access_token'], array( 'photo' => TRUE ) );
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param mixed $details Details as they were passed to the exception thrown in authenticate()
|
||||
* @return void
|
||||
*/
|
||||
public static function link( \IPS\Member $member, $details )
|
||||
{
|
||||
$userData = array();
|
||||
foreach ( \IPS\Http\Url::external( "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address)?oauth2_access_token={$details}" )->request()->get()->decodeXml() as $k => $v )
|
||||
{
|
||||
$userData[ $k ] = (string) $v;
|
||||
}
|
||||
|
||||
$member->linkedin_id = $userData['id'];
|
||||
$member->linkedin_token = $details;
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
\IPS\Output::i()->sidebar['actions'] = array(
|
||||
'help' => array(
|
||||
'title' => 'help',
|
||||
'icon' => 'question-circle',
|
||||
'link' => \IPS\Http\Url::ips( 'docs/login_linkedin' ),
|
||||
'target' => '_blank',
|
||||
'class' => ''
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'api_key' => new \IPS\Helpers\Form\Text( 'login_linkedin_key', ( isset( $this->settings['api_key'] ) ) ? $this->settings['api_key'] : '', TRUE ),
|
||||
'secret_key' => new \IPS\Helpers\Form\Text( 'login_linkedin_secret', ( isset( $this->settings['secret_key'] ) ) ? $this->settings['secret_key'] : '', TRUE ),
|
||||
'real_name' => new \IPS\Helpers\Form\YesNo( 'login_real_name', ( isset( $this->settings['real_name'] ) ) ? $this->settings['real_name'] : FALSE, TRUE )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \UnexpectedValueException If response code is not 200
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://www.linkedin.com/uas/oauth2/authorization" )->setQueryString( array(
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->settings['api_key'],
|
||||
'scope' => 'r_basicprofile r_emailaddress',
|
||||
'state' => \IPS\Session::i()->csrfKey,
|
||||
'redirect_uri' => \IPS\Settings::i()->base_url
|
||||
) )->request()->get();
|
||||
|
||||
if ( $response->httpResponseCode != 200 )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Linkedin') ) ) ) );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Linkedin') ) ) ) );
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
return ( $member->linkedin_id and $member->linkedin_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Windows Live Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows Live Login Handler
|
||||
*/
|
||||
class _Live extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'windows';
|
||||
|
||||
/**
|
||||
* Get Form
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param bool $ucp Is UCP? (as opposed to login form)
|
||||
* @return string
|
||||
*/
|
||||
public function loginForm( $url, $ucp=FALSE )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->live( (string) \IPS\Http\Url::external( 'https://login.live.com/oauth20_authorize.srf' )->setQueryString( array(
|
||||
'client_id' => $this->settings['client_id'],
|
||||
'scope' => 'wl.signin wl.emails wl.offline_access',
|
||||
'response_type' => 'code',
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/microsoft/auth.php', 'none' ),
|
||||
'state' => ( $ucp ? 'ucp' : \IPS\Dispatcher::i()->controllerLocation )
|
||||
) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $url, $member=NULL )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Send HTTP request */
|
||||
$response = \IPS\Http\Url::external( 'https://login.live.com/oauth20_token.srf' )->request()->post( array(
|
||||
'client_id' => $this->settings['client_id'],
|
||||
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/microsoft/auth.php', 'none' ),
|
||||
'client_secret' => $this->settings['client_secret'],
|
||||
'code' => \IPS\Request::i()->code,
|
||||
'grant_type' => 'authorization_code'
|
||||
) )->decodeJson();
|
||||
|
||||
/* Check the response */
|
||||
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Get user data */
|
||||
$userData = \IPS\Http\Url::external( "https://apis.live.net/v5.0/me?access_token={$response['access_token']}" )->request()->get()->decodeJson();
|
||||
|
||||
/* Find or create member */
|
||||
$member = $this->createOrUpdateAccount( $member ?: \IPS\Member::load( $userData['id'], 'live_id' ), array(
|
||||
'live_id' => $userData['id'],
|
||||
'live_token' => $response['refresh_token'],
|
||||
), $this->settings['real_name'] ? $userData['name'] : NULL, $userData['emails']['preferred'], $response['access_token'], array( 'photo' => TRUE, 'cover' => FALSE, 'status' => '' ), 'IPS\core\ProfileSync\Microsoft' );
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param mixed $details Details as they were passed to the exception thrown in authenticate()
|
||||
* @return void
|
||||
*/
|
||||
public static function link( \IPS\Member $member, $details )
|
||||
{
|
||||
$userData = \IPS\Http\Url::external( "https://apis.live.net/v5.0/me?access_token={$details}" )->request()->get()->decodeJson();
|
||||
$member->live_id = $userData['id'];
|
||||
$member->live_token = $details;
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
\IPS\Output::i()->sidebar['actions'] = array(
|
||||
'help' => array(
|
||||
'title' => 'help',
|
||||
'icon' => 'question-circle',
|
||||
'link' => \IPS\Http\Url::ips( 'docs/login_live' ),
|
||||
'target' => '_blank',
|
||||
'class' => ''
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'client_id' => new \IPS\Helpers\Form\Text( 'login_live_client', ( isset( $this->settings['client_id'] ) ) ? $this->settings['client_id'] : '', TRUE ),
|
||||
'client_secret' => new \IPS\Helpers\Form\Text( 'login_live_secret', ( isset( $this->settings['client_secret'] ) ) ? $this->settings['client_secret'] : '', TRUE ),
|
||||
'real_name' => new \IPS\Helpers\Form\YesNo( 'login_real_name', ( isset( $this->settings['real_name'] ) ) ? $this->settings['real_name'] : FALSE, TRUE )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
* @throws \UnexpectedValueException If response code is not 200
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://login.live.com/oauth20_authorize.srf?client_id={$this->settings['client_id']}&scope=wl.signin%20wl.emails&response_type=code&redirect_uri=" )->request()->get();
|
||||
if ( $response->httpResponseCode != 200 )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Live') ) ) ) );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Live') ) ) ) );
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
return ( $member->live_id and $member->live_token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 13 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login Handler
|
||||
*/
|
||||
class _Login
|
||||
{
|
||||
const AUTH_TYPE_USERNAME = 1;
|
||||
const AUTH_TYPE_EMAIL = 2;
|
||||
|
||||
/**
|
||||
* @brief Handlers
|
||||
*/
|
||||
public static $handlers = NULL;
|
||||
|
||||
/**
|
||||
* @brief All handlers
|
||||
*/
|
||||
public static $allHandlers = NULL;
|
||||
|
||||
/**
|
||||
* 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 bee updated for IPS4 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 IPS4 for example */ }
|
||||
}
|
||||
|
||||
if( \IPS\Dispatcher::i()->controllerLocation == 'front' )
|
||||
{
|
||||
static::$allHandlers = static::$handlers;
|
||||
}
|
||||
}
|
||||
|
||||
return static::$handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL page for the login screen
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Http\Url $url )
|
||||
{
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the URL to redirect to
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
public static function getDestination()
|
||||
{
|
||||
$ref = NULL;
|
||||
/* If there's an explicit ref value, go there */
|
||||
if( !empty( \IPS\Request::i()->ref ) )
|
||||
{
|
||||
$ref = new \IPS\Http\Url( @base64_decode( \IPS\Request::i()->ref ) ?: \IPS\Request::i()->ref );
|
||||
return $ref;
|
||||
}
|
||||
|
||||
/* Don't redirect to an external domain unless explicitly requested, and don't redirect back to ACP */
|
||||
if( isset( $_SERVER['HTTP_REFERER'] ) AND empty( \IPS\Request::i()->ips_force_return ) )
|
||||
{
|
||||
if( parse_url( \IPS\Settings::i()->base_url, PHP_URL_HOST ) != parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_HOST ) )
|
||||
{
|
||||
unset( $_SERVER['HTTP_REFERER'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$ourBaseReferer = str_replace( \IPS\Settings::i()->base_url, '', $_SERVER['HTTP_REFERER'] );
|
||||
|
||||
if( mb_strpos( $ourBaseReferer, \IPS\CP_DIRECTORY ) === 0 )
|
||||
{
|
||||
unset( $_SERVER['HTTP_REFERER'] );
|
||||
}
|
||||
}
|
||||
|
||||
$ref = new \IPS\Http\Url( $_SERVER['HTTP_REFERER'] );
|
||||
}
|
||||
|
||||
return isset( $ref ) ? $ref->stripQueryString( 'csrfKey' ) : \IPS\Http\Url::internal( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the URL to redirect to following registration, if any
|
||||
*
|
||||
* @param \IPS\Member $member The member that just registered
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
public static function getRegistrationDestination( $member )
|
||||
{
|
||||
foreach ( static::handlers() as $key => $handler )
|
||||
{
|
||||
if ( method_exists( $handler, 'getRegistrationDestination' ) )
|
||||
{
|
||||
return $handler->getRegistrationDestination( $member );
|
||||
}
|
||||
}
|
||||
|
||||
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 \IPS\Http\Url::internal( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Login Forms
|
||||
*
|
||||
* @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 )
|
||||
{
|
||||
if ( $this->forms === NULL )
|
||||
{
|
||||
$this->forms = array();
|
||||
$standardTypes = 0;
|
||||
|
||||
foreach ( static::handlers() as $key => $handler )
|
||||
{
|
||||
if ( method_exists( $handler, 'loginForm' ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->forms[ $key ] = $handler->loginForm( $this->url );
|
||||
}
|
||||
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( '_loginType' => $standardTypes ), NULL, NULL, NULL, 'auth' ) );
|
||||
$standardForm->add( new \IPS\Helpers\Form\Password( 'password', NULL, TRUE, array(), 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_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, '' );
|
||||
}
|
||||
}
|
||||
|
||||
$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 ) ) )
|
||||
{
|
||||
/* Set any flags */
|
||||
foreach ( array_keys( $this->flags ) as $k )
|
||||
{
|
||||
if ( isset( $values[ $k ] ) and $values[ $k ] )
|
||||
{
|
||||
$this->flags[ $k ] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Authenticate */
|
||||
$member = NULL;
|
||||
try
|
||||
{
|
||||
if ( $handler === '_standard' )
|
||||
{
|
||||
$values['auth'] = mb_strtolower( $values['auth'] );
|
||||
$member = $this->authenticateStandard( $values );
|
||||
}
|
||||
else
|
||||
{
|
||||
$member = $handlers[ $handler ]->authenticate( is_object( $form ) ? $values : $this->url );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Login\Exception $e )
|
||||
{
|
||||
/* 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 );
|
||||
}
|
||||
|
||||
/* If we're still here, throw the error we got */
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/* If we passed, log in! */
|
||||
if ( $member->member_id )
|
||||
{
|
||||
/* http://community.invisionpower.com/4bugtrack/upgrading-within-admincp-r3097 - we can't find any reason not checking this is desired at this time */
|
||||
//if( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation != 'setup' )
|
||||
//{
|
||||
/* Check if the account is locked */
|
||||
$this->checkIfAccountIsLocked( $member );
|
||||
|
||||
/* 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;
|
||||
foreach ( $failedLogins as $ipAddress => $times )
|
||||
{
|
||||
foreach ( $times as $k => $v )
|
||||
{
|
||||
if ( $v < $removeLoginsOlderThan->getTimestamp() )
|
||||
{
|
||||
unset( $failedLogins[ $ipAddress ][ $k ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
$member->failed_logins = $failedLogins;
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
{
|
||||
$member = $handlers[ $key ]->authenticate( $values );
|
||||
break;
|
||||
}
|
||||
catch ( \IPS\Login\Exception $e )
|
||||
{
|
||||
if ( $leastOffensiveException === NULL or $leastOffensiveException->getCode() > $e->getCode() )
|
||||
{
|
||||
$leastOffensiveException = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $member === NULL )
|
||||
{
|
||||
throw $leastOffensiveException;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $member;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an account is locked
|
||||
*
|
||||
* @param \IPS\Member $member The account
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function checkIfAccountIsLocked( $member )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
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' ) );
|
||||
$timeToUnlock = $unlockTime->diff( new DateTime() );
|
||||
|
||||
/* If Unlock Time is in the past, return FALSE to avoid the exception and allow login */
|
||||
if ( $unlockTime->getTimestamp() < time() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_locked_unlock', FALSE, array( 'pluralize' => array( $timeToUnlock->format('%i') ) ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'login_err_locked_nounlock' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare hashes in fixed length, time constant manner.
|
||||
*
|
||||
* @param string $expected The expected hash
|
||||
* @param string $provided The provided input
|
||||
* @return boolean
|
||||
*/
|
||||
public static function compareHashes( $expected, $provided )
|
||||
{
|
||||
if ( !is_string( $expected ) || !is_string( $provided ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$len = \strlen( $expected );
|
||||
if ( $len !== \strlen( $provided ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$status = 0;
|
||||
for ( $i = 0; $i < $len; $i++ )
|
||||
{
|
||||
$status |= ord( $expected[ $i ] ) ^ ord( $provided[ $i ] );
|
||||
}
|
||||
|
||||
return $status === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Abstract Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 15 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Login Handler
|
||||
*/
|
||||
abstract class _LoginAbstract extends \IPS\Node\Model
|
||||
{
|
||||
/**
|
||||
* @brief [ActiveRecord] Multiton Store
|
||||
*/
|
||||
protected static $multitons;
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Table
|
||||
*/
|
||||
public static $databaseTable = 'core_login_handlers';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Prefix
|
||||
*/
|
||||
public static $databasePrefix = 'login_';
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] ID Database Column
|
||||
*/
|
||||
public static $databaseColumnId = 'key';
|
||||
|
||||
/**
|
||||
* @brief [Node] Node Title
|
||||
*/
|
||||
public static $nodeTitle = 'login_handlers';
|
||||
|
||||
/**
|
||||
* @brief [Node] Order Database Column
|
||||
*/
|
||||
public static $databaseColumnOrder = 'order';
|
||||
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'lock';
|
||||
|
||||
/**
|
||||
* @brief Disable the copy button - useful when the forms are very distinctly different
|
||||
*/
|
||||
public $noCopyButton = TRUE;
|
||||
|
||||
/**
|
||||
* Construct ActiveRecord from database row
|
||||
*
|
||||
* @param array $data Row from database table
|
||||
* @param bool $updateMultitonStoreIfExists Replace current object in multiton store if it already exists there?
|
||||
* @return static
|
||||
*/
|
||||
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
|
||||
{
|
||||
/* Initiate an object */
|
||||
$classname = '\\IPS\Login\\' . ucfirst( $data['login_key'] );
|
||||
if ( !class_exists( $classname ) )
|
||||
{
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
|
||||
$obj = new $classname;
|
||||
$obj->_new = FALSE;
|
||||
|
||||
/* Import data */
|
||||
foreach ( $data as $k => $v )
|
||||
{
|
||||
if( static::$databasePrefix )
|
||||
{
|
||||
$k = \substr( $k, \strlen( static::$databasePrefix ) );
|
||||
}
|
||||
|
||||
$obj->$k = $v;
|
||||
}
|
||||
$obj->changed = array();
|
||||
$obj->init();
|
||||
|
||||
/* Return */
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get Node Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get__title()
|
||||
{
|
||||
$key = "login_handler_" . ucfirst($this->key);
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get the log key
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get__logKey()
|
||||
{
|
||||
return "login_handler_" . ucfirst($this->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Get whether or not this node is enabled
|
||||
*
|
||||
* @note Return value NULL indicates the node cannot be enabled/disabled
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function get__enabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Set whether or not this node is enabled
|
||||
*
|
||||
* @param bool $value Enable Node?
|
||||
* @return void
|
||||
*/
|
||||
protected function set__enabled( $value )
|
||||
{
|
||||
$this->enabled = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Does the currently logged in user have permission to add a child node to this node?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canAdd()
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Does the currently logged in user have permission to add aa root node?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function canAddRoot()
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Add/Edit Form
|
||||
*
|
||||
* @param \IPS\Helpers\Form $form The form
|
||||
* @return void
|
||||
*/
|
||||
public function form( &$form )
|
||||
{
|
||||
foreach ( $this->acpForm() as $k => $v )
|
||||
{
|
||||
if ( is_string( $v ) )
|
||||
{
|
||||
$form->addHeader( $v );
|
||||
}
|
||||
else
|
||||
{
|
||||
$form->add( $v );
|
||||
$names[ $v->name ] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->key !== 'internal' )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'login_acp', $this->acp ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Format form values from add/edit form for save
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
{
|
||||
$names = array();
|
||||
foreach ( $this->acpForm() as $k => $v )
|
||||
{
|
||||
if ( !is_string( $v ) )
|
||||
{
|
||||
$names[ $v->name ] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
$save = array();
|
||||
foreach ( $names as $formName => $saveName )
|
||||
{
|
||||
if( array_key_exists( $formName, $values ) )
|
||||
{
|
||||
$save[ $saveName ] = $values[ $formName ];
|
||||
unset( $values[ $formName ] );
|
||||
}
|
||||
}
|
||||
|
||||
$values['settings'] = json_encode( $save );
|
||||
$this->settings = $values['settings'];
|
||||
|
||||
$this->testSettings();
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Save Changed Columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function save()
|
||||
{
|
||||
parent::save();
|
||||
unset( \IPS\Data\Store::i()->loginHandlers );
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
unset( \IPS\Data\Store::i()->loginHandlers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account from login - checks registration is enabled, the name/email doesn't already exists and calls the spam service
|
||||
*
|
||||
* @param $member|NULL \IPS\Member The existing member, if one exists
|
||||
* @param $memberProperties array Any properties to set on the member (whether registering or not) such as IDs from third-party services
|
||||
* @param $name string|NULL The desired username. If not provided, or another existing user has this name, it will be left blank and the user prompted to provide it.
|
||||
* @param $email string|NULL The user's email address. If it matches an existing account, an \IPS\Login\Exception object will be thrown so the user can be prompted to link those accounts. If not provided, it will be left blank and the user prompted to provide it.
|
||||
* @param $details mixed If $email matches an existing account, this is wgat will later be provided to link() - include any data you will need to link the accounts later
|
||||
* @param $profileSync array|NULL If creating a new account, the default profile sync settings for this provider
|
||||
* @param $profileSyncClass string|NULL If $profileSync is enabled, the profile sync service with a name matching this login handler will be used. Provide an alternative classname to override (e.g. for Windows login, the login handler class is Live, but the profile sync class is Microsoft)
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception If email address matches (\IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT), registration is disabled (IPS\Login\Exception::REGISTRATION_DISABLED) or the spam service denies registration (\IPS\Login\Exception::REGISTRATION_DENIED_BY_SPAM_SERVICE)
|
||||
*/
|
||||
protected function createOrUpdateAccount( $member, $memberProperties=array(), $name=NULL, $email=NULL, $details=NULL, $profileSync=NULL, $profileSyncClass=NULL )
|
||||
{
|
||||
/* Create an account */
|
||||
if ( !$member or !$member->member_id )
|
||||
{
|
||||
/* Is registraion enabled? */
|
||||
if( !\IPS\Settings::i()->allow_reg )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'reg_disabled', \IPS\Login\Exception::REGISTRATION_DISABLED );
|
||||
$exception->handler = mb_substr( get_called_class(), 10 );
|
||||
$exception->member = $member;
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
/* Init */
|
||||
$member = new \IPS\Member;
|
||||
$member->member_group_id = \IPS\Settings::i()->member_group;
|
||||
|
||||
/* If we admin validation is enabled, set that flag */
|
||||
if ( \IPS\Settings::i()->reg_auth_type == 'admin' or \IPS\Settings::i()->reg_auth_type == 'admin_user' )
|
||||
{
|
||||
$member->members_bitoptions['validating'] = TRUE;
|
||||
}
|
||||
|
||||
/* Set name - if it already exists, we'll leave it blank and they'll be prompted to fill it in */
|
||||
if ( $name !== NULL )
|
||||
{
|
||||
$existingUsername = \IPS\Member::load( $name, 'name' );
|
||||
if ( !$existingUsername->member_id )
|
||||
{
|
||||
$member->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set email */
|
||||
if ( $email !== NULL )
|
||||
{
|
||||
/* Check it doesn't already exist */
|
||||
$existingEmail = \IPS\Member::load( $email, 'email' );
|
||||
if ( $existingEmail->member_id )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception->handler = mb_substr( get_called_class(), 10 );
|
||||
$exception->member = $existingEmail;
|
||||
if ( $details )
|
||||
{
|
||||
$exception->details = $details;
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
/* Set it */
|
||||
$member->email = $email;
|
||||
|
||||
/* Check the spam service is okay with it */
|
||||
if( \IPS\Settings::i()->spam_service_enabled )
|
||||
{
|
||||
if( $member->spamService() == 4 )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'spam_denied_account', \IPS\Login\Exception::REGISTRATION_DENIED_BY_SPAM_SERVICE );
|
||||
$exception->handler = mb_substr( get_called_class(), 10 );
|
||||
$exception->member = $member;
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set profile sync */
|
||||
if ( $profileSync !== NULL )
|
||||
{
|
||||
$member->profilesync = json_encode( array( mb_substr( get_called_class(), 10 ) => $profileSync ) );
|
||||
}
|
||||
|
||||
/* Member properties */
|
||||
foreach ( $memberProperties as $k => $v )
|
||||
{
|
||||
$member->$k = $v;
|
||||
}
|
||||
$member->save();
|
||||
|
||||
/* Users created by log in don't need to validate their email if the service provided it,
|
||||
so if we admin and user validation is required, set that the user validated */
|
||||
if ( \IPS\Settings::i()->reg_auth_type == 'admin_user' )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_validating', array( 'user_verified' => 1 ), array( 'member_id=?', $member->member_id ) );
|
||||
}
|
||||
|
||||
/* Sync photo, etc now */
|
||||
if ( $profileSync !== NULL )
|
||||
{
|
||||
$profileSyncClass = $profileSyncClass ?: 'IPS\core\ProfileSync\\' . mb_substr( get_called_class(), 10 );
|
||||
$sync = new $profileSyncClass( $member );
|
||||
$sync->sync();
|
||||
}
|
||||
}
|
||||
|
||||
/* Or just update? */
|
||||
else
|
||||
{
|
||||
foreach ( $memberProperties as $k => $v )
|
||||
{
|
||||
$member->$k = $v;
|
||||
}
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_settings()
|
||||
{
|
||||
return json_decode( $this->_data['settings'], TRUE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the type as a textual string
|
||||
*
|
||||
* @param int $type The valid configured types
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginType( $type )
|
||||
{
|
||||
switch ( $type )
|
||||
{
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
return 'username_or_email';
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME:
|
||||
return 'username';
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
return 'email_address';
|
||||
break;
|
||||
}
|
||||
|
||||
return 'username';
|
||||
}
|
||||
|
||||
/**
|
||||
* Email is in use?
|
||||
* Used when registering or changing an email address to check the new one is available
|
||||
*
|
||||
* @param string $email Email Address
|
||||
* @param \IPS\Member|NULL $eclude Member to exclude
|
||||
* @return bool|NULL Boolean indicates if email is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function emailIsInUse( $email, \IPS\Member $exclude=NULL )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Username is in use?
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @return bool|NULL Boolean indicates if username is in use (TRUE means is in use and thus not registerable) or NULL if this handler does not support such an API
|
||||
*/
|
||||
public function usernameIsInUse( $username )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldEmail Old Email Address
|
||||
* @param string $newEmail New Email Address
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail )
|
||||
{
|
||||
if ( !$this->canChange( 'email', $member ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $newPassword New Password
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
{
|
||||
if ( !$this->canChange( 'password', $member ) )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an account off
|
||||
*
|
||||
* @param \IPS\Member $member The member that was just logged out
|
||||
* @param \IPS\Http\Url $redirectUrl The URL to send the user back to
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @note This is NOT called if you force log out all users from the ACP on an individual site
|
||||
*/
|
||||
public function logoutAccount( \IPS\Member $member, \IPS\Http\Url $redirectUrl )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account
|
||||
*
|
||||
* @param \IPS\Member $member The member that was just created
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createAccount( \IPS\Member $member )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function validateAccount( \IPS\Member $member )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteAccount( \IPS\Member $member )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Ban or unban account
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param bool TRUE means member is being banned, FALSE means they are being unbanned
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function banAccount( \IPS\Member $member, $ban=TRUE )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two accounts
|
||||
*
|
||||
* @param \IPS\Member $member The member to keep with original data
|
||||
* @param \IPS\Member $member2 The member that will be deleted
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function mergeAccounts( \IPS\Member $member, \IPS\Member $member2 )
|
||||
{
|
||||
// By default do nothing. Handlers can extend.
|
||||
}
|
||||
|
||||
/**
|
||||
* Search
|
||||
*
|
||||
* @param string $column Column to search
|
||||
* @param string $query Search query
|
||||
* @param string|null $order Column to order by
|
||||
* @param mixed $where Where clause
|
||||
* @return array
|
||||
*/
|
||||
public static function search( $column, $query, $order, $where=array() )
|
||||
{
|
||||
if ( $column === '_title' )
|
||||
{
|
||||
$return = array();
|
||||
foreach ( \IPS\Member::loggedIn()->language()->searchCustom( 'login_handler_', $query, TRUE ) as $key => $value )
|
||||
{
|
||||
try
|
||||
{
|
||||
$return[ $key ] = self::load( $key );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Twitter Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 18 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Login Handler
|
||||
*/
|
||||
class _Twitter extends LoginAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Icon
|
||||
*/
|
||||
public static $icon = 'twitter';
|
||||
|
||||
/**
|
||||
* Get Form
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL for the login page
|
||||
* @return string
|
||||
*/
|
||||
public function loginForm( \IPS\Http\Url $url )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->twitter( $url->setQueryString( 'loginProcess', 'twitter' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param string $url The URL for the login page
|
||||
* @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $url, $member=NULL )
|
||||
{
|
||||
if ( isset( \IPS\Request::i()->denied ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
/* Get a request token */
|
||||
if ( !isset( \IPS\Request::i()->oauth_token ) )
|
||||
{
|
||||
$response = $this->sendRequest( 'get', 'https://api.twitter.com/oauth/request_token', array( 'oauth_callback' => (string) $url->setQueryString( 'loginProcess', 'twitter' ) ) )->decodeQueryString('oauth_token');
|
||||
\IPS\Output::i()->redirect( "https://api.twitter.com/oauth/authenticate?oauth_token={$response['oauth_token']}" );
|
||||
}
|
||||
|
||||
/* Authenticate */
|
||||
$response = $this->sendRequest( 'post', 'https://api.twitter.com/oauth/access_token', array( 'oauth_verifier' => \IPS\Request::i()->oauth_verifier ), \IPS\Request::i()->oauth_token )->decodeQueryString('user_id');
|
||||
|
||||
/* What name are we using? */
|
||||
$member = $member ?: \IPS\Member::load( $response['user_id'], 'twitter_id' );
|
||||
$name = NULL;
|
||||
if ( !$member or !$member->member_id )
|
||||
{
|
||||
if ( $this->settings['name'] == 'screen' )
|
||||
{
|
||||
$name = $response['screen_name'];
|
||||
}
|
||||
elseif ( $this->settings['name'] == 'real' )
|
||||
{
|
||||
try
|
||||
{
|
||||
$user = $this->sendRequest( 'get', 'https://api.twitter.com/1.1/account/verify_credentials.json', array(), $response['oauth_token'], $response['oauth_token_secret'] )->decodeJson();
|
||||
$name = $user['name'];
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
/* Find or create member */
|
||||
$member = $this->createOrUpdateAccount( $member, array(
|
||||
'twitter_id' => $response['user_id'],
|
||||
'twitter_token' => $response['oauth_token'],
|
||||
'twitter_secret' => $response['oauth_token_secret']
|
||||
), $name, NULL, NULL, array( 'photo' => TRUE, 'cover' => TRUE, 'status' => '' ) );
|
||||
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Login\Exception $e )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @param string $url URL to redirect user to after successful submission
|
||||
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
\IPS\Output::i()->sidebar['actions'] = array(
|
||||
'help' => array(
|
||||
'title' => 'help',
|
||||
'icon' => 'question-circle',
|
||||
'link' => \IPS\Http\Url::ips( 'docs/login_twitter' ),
|
||||
'target' => '_blank',
|
||||
'class' => ''
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'consumer_key' => new \IPS\Helpers\Form\Text( 'login_twitter_key', ( isset( $this->settings['consumer_key'] ) ) ? $this->settings['consumer_key'] : '', TRUE ),
|
||||
'consumer_secret' => new \IPS\Helpers\Form\Text( 'login_twitter_secret', ( isset( $this->settings['consumer_secret'] ) ) ? $this->settings['consumer_secret'] : '', TRUE ),
|
||||
'name' => new \IPS\Helpers\Form\Radio( 'login_twitter_name', ( isset( $this->settings['name'] ) ) ? $this->settings['name'] : 'any', TRUE, array( 'options' => array(
|
||||
'real' => 'login_twitter_name_real',
|
||||
'screen' => 'login_twitter_name_screen',
|
||||
'any' => 'login_twitter_name_any',
|
||||
) ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function testSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = $this->sendRequest( 'get', 'https://api.twitter.com/oauth/request_token', array( 'oauth_callback' => (string) \IPS\Http\Url::internal( '', 'front' ) ) )->decodeQueryString('oauth_token');
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Twitter') ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Request
|
||||
*
|
||||
* @param string $method HTTP Method
|
||||
* @param string $url URL
|
||||
* @param array $params Parameters
|
||||
* @param string $token OAuth Token
|
||||
* @return \IPS\Http\Response
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
*/
|
||||
public function sendRequest( $method, $url, $params=array(), $token='', $secret='' )
|
||||
{
|
||||
/* Generate the OAUTH Authorization Header */
|
||||
$OAuthAuthorization = array_merge( array(
|
||||
'oauth_consumer_key' => $this->settings['consumer_key'],
|
||||
'oauth_nonce' => md5( uniqid() ),
|
||||
'oauth_signature_method'=> 'HMAC-SHA1',
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_token' => $token,
|
||||
'oauth_version' => '1.0'
|
||||
) );
|
||||
|
||||
foreach ( $params as $k => $v )
|
||||
{
|
||||
if ( mb_substr( $k, 0, 6 ) === 'oauth_' )
|
||||
{
|
||||
$OAuthAuthorization = array_merge( array( $k => $v ), $OAuthAuthorization );
|
||||
unset( $params[ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
$signatureBaseString = mb_strtoupper( $method ) . '&' . rawurlencode( $url ) . '&' . rawurlencode( http_build_query( $OAuthAuthorization ) ) . ( count( $params ) ? ( rawurlencode( '&' ) . rawurlencode( http_build_query( $params, NULL, NULL, PHP_QUERY_RFC3986 ) ) ) : '' );
|
||||
$signingKey = rawurlencode( $this->settings['consumer_secret'] ) . '&' . rawurlencode( $secret ?: $token );
|
||||
$OAuthAuthorizationEncoded = array();
|
||||
foreach ( $OAuthAuthorization as $k => $v )
|
||||
{
|
||||
$OAuthAuthorizationEncoded[] = rawurlencode( $k ) . '="' . rawurlencode( $v ) . '"';
|
||||
|
||||
if ( $k === 'oauth_nonce' )
|
||||
{
|
||||
$signature = base64_encode( hash_hmac( 'sha1', $signatureBaseString, $signingKey, TRUE ) );
|
||||
$OAuthAuthorizationEncoded[] = rawurlencode( 'oauth_signature' ) . '="' . rawurlencode( $signature ) . '"';
|
||||
}
|
||||
}
|
||||
$OAuthAuthorizationHeader = 'OAuth ' . implode( ', ', $OAuthAuthorizationEncoded );
|
||||
|
||||
/* Send the request */
|
||||
return \IPS\Http\Url::external( $url )->request()->setHeaders( array( 'Authorization' => $OAuthAuthorizationHeader ) )->$method( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member sign in with this login handler?
|
||||
* Used to ensure when a user disassociates a social login that they have some other way of logging in
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
{
|
||||
return ( $member->twitter_id and $member->twitter_token and $member->twitter_secret );
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member change their email/password with this login handler?
|
||||
*
|
||||
* @param string $type 'email' or 'password'
|
||||
* @param \IPS\Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChange( $type, \IPS\Member $member )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Internal Login Handler
|
||||
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
|
||||
* @license http://www.invisionpower.com/legal/standards/
|
||||
* @package IPS Social Suite
|
||||
* @since 13 Mar 2013
|
||||
* @version SVN_VERSION_NUMBER
|
||||
*/
|
||||
|
||||
namespace IPS\Login;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade Login Handler
|
||||
*/
|
||||
class _Upgrade extends \IPS\Login\Internal
|
||||
{
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param array $values Values from from
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function authenticate( $values )
|
||||
{
|
||||
/* Get member(s) */
|
||||
$members = array();
|
||||
|
||||
$table = 'core_members';
|
||||
|
||||
if ( \IPS\Db::i()->checkForTable( 'members' ) AND !\IPS\Db::i()->checkForTable( 'core_members' ) )
|
||||
{
|
||||
$table = 'members';
|
||||
}
|
||||
|
||||
foreach( \IPS\Db::i()->select('*', $table, array( 'name=? or email=?', \IPS\Request::legacyEscape( $values['auth'] ), \IPS\Request::legacyEscape( $values['auth'] ) ) ) as $_member )
|
||||
{
|
||||
$members[] = \IPS\Member::constructFromData( $_member );
|
||||
}
|
||||
|
||||
/* If we didn't match any, throw an exception */
|
||||
if ( empty( $members ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $this->getLoginType( $this->authTypes ) ) ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Check the password for each possible account */
|
||||
foreach ( $members as $member )
|
||||
{
|
||||
if ( \IPS\Login::compareHashes( $member->members_pass_hash, $member->encryptedPassword( $values['password'] ) ) OR
|
||||
\IPS\Login::compareHashes( $member->members_pass_hash, $member->encryptedPassword( \IPS\Request::legacyEscape( $values['password'] ) ) ) )
|
||||
{
|
||||
/* Return */
|
||||
return $member;
|
||||
}
|
||||
}
|
||||
|
||||
/* Still here? Throw a password incorrect exception */
|
||||
throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
Reference in new issue
Block a user