Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
@@ -11,9 +11,16 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -27,9 +34,9 @@ trait ButtonHandler
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function type()
|
||||
public function type(): int
|
||||
{
|
||||
return \IPS\Login::TYPE_BUTTON;
|
||||
return Login::TYPE_BUTTON;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,47 +44,46 @@ trait ButtonHandler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function button()
|
||||
public function button(): string
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->loginButton( $this );
|
||||
return Theme::i()->getTemplate( 'login', 'core', 'global' )->loginButton( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @throws Exception
|
||||
*/
|
||||
abstract public function authenticateButton( \IPS\Login $login );
|
||||
abstract public function authenticateButton( Login $login );
|
||||
|
||||
/**
|
||||
* Get the button color
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function buttonColor();
|
||||
abstract public function buttonColor(): string;
|
||||
|
||||
/**
|
||||
* Get the button icon
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function buttonIcon();
|
||||
abstract public function buttonIcon(): string;
|
||||
|
||||
/**
|
||||
* Get button text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function buttonText();
|
||||
abstract public function buttonText(): string;
|
||||
|
||||
/**
|
||||
* Get button class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
@@ -85,9 +91,9 @@ trait ButtonHandler
|
||||
/**
|
||||
* Get logo to display in user cp sidebar
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForUcp()
|
||||
public function logoForUcp(): Url|string|null
|
||||
{
|
||||
return $this->buttonIcon();
|
||||
}
|
||||
|
||||
@@ -11,21 +11,43 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Db;
|
||||
use IPS\Db\Exception as DbException;
|
||||
use IPS\Helpers\Form\Codemirror;
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Member;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use UnderflowException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Internal Database Login Handler
|
||||
*/
|
||||
class _ExternalDatabase extends \IPS\Login\Handler
|
||||
class ExternalDatabase extends Handler
|
||||
{
|
||||
/**
|
||||
* @brief Can we have multiple instances of this handler?
|
||||
*/
|
||||
public static $allowMultiple = TRUE;
|
||||
public static bool $allowMultiple = TRUE;
|
||||
|
||||
use UsernamePasswordHandler;
|
||||
|
||||
@@ -36,7 +58,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_External';
|
||||
}
|
||||
@@ -44,43 +66,42 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* 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_methods.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$id = $this->id ?: 'new';
|
||||
|
||||
$return = array(
|
||||
'login_external_conn',
|
||||
'sql_host' => new \IPS\Helpers\Form\Text( 'login_external_host', ( isset( $this->settings['sql_host'] ) and $this->settings['sql_host'] ) ? $this->settings['sql_host'] : 'localhost', TRUE ),
|
||||
'sql_user' => new \IPS\Helpers\Form\Text( 'login_external_user', isset( $this->settings['sql_user'] ) ? $this->settings['sql_user'] : NULL, TRUE ),
|
||||
'sql_pass' => new \IPS\Helpers\Form\Text( 'login_external_pass', isset( $this->settings['sql_pass'] ) ? $this->settings['sql_pass'] : NULL, FALSE ),
|
||||
'sql_database' => new \IPS\Helpers\Form\Text( 'login_external_database', isset( $this->settings['sql_database'] ) ? $this->settings['sql_database'] : NULL, TRUE ),
|
||||
'sql_port' => new \IPS\Helpers\Form\Number( 'login_external_port', isset( $this->settings['sql_port'] ) ? $this->settings['sql_port'] : 3306, FALSE ),
|
||||
'sql_socket' => new \IPS\Helpers\Form\Text( 'login_external_socket', isset( $this->settings['sql_socket'] ) ? $this->settings['sql_socket'] : NULL, FALSE ),
|
||||
'sql_host' => new Text( 'login_external_host', ( isset( $this->settings['sql_host'] ) and $this->settings['sql_host'] ) ? $this->settings['sql_host'] : 'localhost', TRUE ),
|
||||
'sql_user' => new Text( 'login_external_user', $this->settings['sql_user'] ?? NULL, TRUE ),
|
||||
'sql_pass' => new Text( 'login_external_pass', $this->settings['sql_pass'] ?? NULL, FALSE ),
|
||||
'sql_database' => new Text( 'login_external_database', $this->settings['sql_database'] ?? NULL, TRUE ),
|
||||
'sql_port' => new Number( 'login_external_port', $this->settings['sql_port'] ?? 3306, FALSE ),
|
||||
'sql_socket' => new Text( 'login_external_socket', $this->settings['sql_socket'] ?? NULL, FALSE ),
|
||||
'login_external_schema',
|
||||
'db_table' => new \IPS\Helpers\Form\Text( 'login_external_table', isset( $this->settings['db_table'] ) ? $this->settings['db_table'] : NULL, TRUE ),
|
||||
'db_col_id' => new \IPS\Helpers\Form\Text( 'login_external_id', isset( $this->settings['db_col_id'] ) ? $this->settings['db_col_id'] : NULL, FALSE ),
|
||||
'db_col_user' => new \IPS\Helpers\Form\Text( 'login_external_username', isset( $this->settings['db_col_user'] ) ? $this->settings['db_col_user'] : NULL, FALSE, array(), function( $val )
|
||||
'db_table' => new Text( 'login_external_table', $this->settings['db_table'] ?? NULL, TRUE ),
|
||||
'db_col_id' => new Text( 'login_external_id', $this->settings['db_col_id'] ?? NULL, FALSE ),
|
||||
'db_col_user' => new Text( 'login_external_username', $this->settings['db_col_user'] ?? NULL, FALSE, array(), function( $val )
|
||||
{
|
||||
if ( !$val and \IPS\Request::i()->login_auth_types & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
if ( !$val and Request::i()->login_auth_types & Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
throw new \DomainException('login_external_username_err');
|
||||
throw new DomainException('login_external_username_err');
|
||||
}
|
||||
} ),
|
||||
'db_col_email' => new \IPS\Helpers\Form\Text( 'login_external_email', isset( $this->settings['db_col_email'] ) ? $this->settings['db_col_email'] : NULL, FALSE, array(), function( $val )
|
||||
'db_col_email' => new Text( 'login_external_email', $this->settings['db_col_email'] ?? NULL, FALSE, array(), function( $val )
|
||||
{
|
||||
if ( !$val and \IPS\Request::i()->login_auth_types & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
if ( !$val and Request::i()->login_auth_types & Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
throw new \DomainException('login_external_email_err');
|
||||
throw new DomainException('login_external_email_err');
|
||||
}
|
||||
} ),
|
||||
'db_col_pass' => new \IPS\Helpers\Form\Text( 'login_external_password', isset( $this->settings['db_col_pass'] ) ? $this->settings['db_col_pass'] : NULL, TRUE ),
|
||||
'db_encryption' => new \IPS\Helpers\Form\Radio( 'login_external_encryption', ( isset( $this->settings['db_encryption'] ) and $this->settings['db_encryption'] ) ? $this->settings['db_encryption'] : NULL, TRUE, array(
|
||||
'db_col_pass' => new Text( 'login_external_password', $this->settings['db_col_pass'] ?? NULL, TRUE ),
|
||||
'db_encryption' => new Radio( 'login_external_encryption', ( isset( $this->settings['db_encryption'] ) and $this->settings['db_encryption'] ) ? $this->settings['db_encryption'] : NULL, TRUE, array(
|
||||
'options' => array(
|
||||
'password_hash' => 'login_external_encryption_password_hash',
|
||||
'md5' => 'MD5',
|
||||
@@ -92,9 +113,9 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
'other' => array( 'db_encryption_hash', 'db_encryption_validate' )
|
||||
)
|
||||
) ),
|
||||
'db_encryption_hash' => new \IPS\Helpers\Form\Codemirror( 'login_external_encryption_hash', isset( $this->settings['db_encryption_hash'] ) ? $this->settings['db_encryption_hash'] : 'return password_hash( $providedPassword );', NULL, array(
|
||||
'db_encryption_hash' => new Codemirror( 'login_external_encryption_hash', $this->settings['db_encryption_hash'] ?? 'return password_hash( $providedPassword );', NULL, array(
|
||||
'mode' => 'php',
|
||||
'tags' => array( '$providedPassword' => \IPS\Member::loggedIn()->language()->addToStack('login_external_encryption_custom_password') )
|
||||
'tags' => array( '$providedPassword' => Member::loggedIn()->language()->addToStack('login_external_encryption_custom_password') )
|
||||
), function( $val )
|
||||
{
|
||||
try
|
||||
@@ -103,16 +124,16 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \DomainException( $e->getMessage() );
|
||||
throw new DomainException( $e->getMessage() );
|
||||
}
|
||||
catch ( \Throwable $e )
|
||||
catch ( Throwable $e )
|
||||
{
|
||||
throw new \DomainException( $e->getMessage() );
|
||||
throw new DomainException( $e->getMessage() );
|
||||
}
|
||||
}, NULL, NULL, 'db_encryption_hash' ),
|
||||
'db_encryption_validate' => new \IPS\Helpers\Form\Codemirror( 'login_external_encryption_validate', isset( $this->settings['db_encryption_validate'] ) ? $this->settings['db_encryption_validate'] : 'return password_verify( $providedPassword, $row[\'password\'] );', NULL, array(
|
||||
'db_encryption_validate' => new Codemirror( 'login_external_encryption_validate', $this->settings['db_encryption_validate'] ?? 'return password_verify( $providedPassword, $row[\'password\'] );', NULL, array(
|
||||
'mode' => 'php',
|
||||
'tags' => array( '$row' => \IPS\Member::loggedIn()->language()->addToStack('login_external_encryption_custom_row'), '$providedPassword' => \IPS\Member::loggedIn()->language()->addToStack('login_external_encryption_custom_password') )
|
||||
'tags' => array( '$row' => Member::loggedIn()->language()->addToStack('login_external_encryption_custom_row'), '$providedPassword' => Member::loggedIn()->language()->addToStack('login_external_encryption_custom_password') )
|
||||
), function( $val )
|
||||
{
|
||||
try
|
||||
@@ -121,49 +142,49 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \DomainException( $e->getMessage() );
|
||||
throw new DomainException( $e->getMessage() );
|
||||
}
|
||||
catch ( \Throwable $e )
|
||||
catch ( Throwable $e )
|
||||
{
|
||||
throw new \DomainException( $e->getMessage() );
|
||||
throw new DomainException( $e->getMessage() );
|
||||
}
|
||||
}, NULL, NULL, 'db_encryption_validate' ),
|
||||
'db_extra' => new \IPS\Helpers\Form\Text( 'login_external_extra', isset( $this->settings['db_extra'] ) ? $this->settings['db_extra'] : NULL ),
|
||||
'db_extra' => new Text( 'login_external_extra', $this->settings['db_extra'] ?? NULL ),
|
||||
'login_settings',
|
||||
'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', isset( $this->settings['auth_types'] ) ? $this->settings['auth_types'] : ( \IPS\Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
), 'toggles' => array( \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => array( 'form_' . $id . '_login_auth_types_warning' ), \IPS\Login::AUTH_TYPE_USERNAME => array( 'form_' . $id . '_login_auth_types_warning' ) ) ) ),
|
||||
'auth_types' => new Select( 'login_auth_types', $this->settings['auth_types'] ?? ( Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
Login::AUTH_TYPE_USERNAME => 'username',
|
||||
), 'toggles' => array( Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => array( 'form_' . $id . '_login_auth_types_warning' ), Login::AUTH_TYPE_USERNAME => array( 'form_' . $id . '_login_auth_types_warning' ) ) ) ),
|
||||
);
|
||||
|
||||
if ( \IPS\Settings::i()->allow_forgot_password == 'normal' or \IPS\Settings::i()->allow_forgot_password == 'handler' )
|
||||
if ( Settings::i()->allow_forgot_password == 'normal' or Settings::i()->allow_forgot_password == 'handler' )
|
||||
{
|
||||
$return['forgot_password_url'] = new \IPS\Helpers\Form\Url( 'handler_forgot_password_url', isset( $this->settings['forgot_password_url'] ) ? $this->settings['forgot_password_url'] : NULL );
|
||||
\IPS\Member::loggedIn()->language()->words['handler_forgot_password_url_desc'] = \IPS\Member::loggedIn()->language()->addToStack( \IPS\Settings::i()->allow_forgot_password == 'normal' ? 'handler_forgot_password_url_desc_normal' : 'handler_forgot_password_url_deschandler' );
|
||||
$return['forgot_password_url'] = new \IPS\Helpers\Form\Url( 'handler_forgot_password_url', $this->settings['forgot_password_url'] ?? NULL );
|
||||
Member::loggedIn()->language()->words['handler_forgot_password_url_desc'] = Member::loggedIn()->language()->addToStack( Settings::i()->allow_forgot_password == 'normal' ? 'handler_forgot_password_url_desc_normal' : 'handler_forgot_password_url_deschandler' );
|
||||
}
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
$return['sync_name_changes'] = new \IPS\Helpers\Form\Radio( 'login_sync_name_changes', isset( $this->settings['sync_name_changes'] ) ? $this->settings['sync_name_changes'] : 1, FALSE, array( 'options' => array(
|
||||
$return['sync_name_changes'] = new Radio( 'login_sync_name_changes', $this->settings['sync_name_changes'] ?? 1, FALSE, array( 'options' => array(
|
||||
1 => 'login_sync_changes_yes',
|
||||
0 => 'login_sync_changes_no',
|
||||
) ) );
|
||||
if ( \IPS\Settings::i()->allow_email_changes == 'normal' )
|
||||
if ( Settings::i()->allow_email_changes == 'normal' )
|
||||
{
|
||||
$return['sync_email_changes'] = new \IPS\Helpers\Form\Radio( 'login_sync_email_changes', isset( $this->settings['sync_email_changes'] ) ? $this->settings['sync_email_changes'] : 1, FALSE, array( 'options' => array(
|
||||
$return['sync_email_changes'] = new Radio( 'login_sync_email_changes', $this->settings['sync_email_changes'] ?? 1, FALSE, array( 'options' => array(
|
||||
1 => 'login_sync_changes_yes',
|
||||
0 => 'login_sync_changes_no',
|
||||
) ) );
|
||||
}
|
||||
if ( \IPS\Settings::i()->allow_password_changes == 'normal' )
|
||||
if ( Settings::i()->allow_password_changes == 'normal' )
|
||||
{
|
||||
$return['sync_password_changes'] = new \IPS\Helpers\Form\Radio( 'login_sync_password_changes', isset( $this->settings['sync_password_changes'] ) ? $this->settings['sync_password_changes'] : 1, FALSE, array( 'options' => array(
|
||||
$return['sync_password_changes'] = new Radio( 'login_sync_password_changes', $this->settings['sync_password_changes'] ?? 1, FALSE, array( 'options' => array(
|
||||
1 => 'login_sync_changes_yes',
|
||||
0 => 'login_sync_password_changes_no',
|
||||
) ) );
|
||||
}
|
||||
|
||||
$return['show_in_ucp'] = new \IPS\Helpers\Form\Radio( 'login_handler_show_in_ucp', isset( $this->settings['show_in_ucp'] ) ? $this->settings['show_in_ucp'] : 'disabled', FALSE, array(
|
||||
$return['show_in_ucp'] = new Radio( 'login_handler_show_in_ucp', $this->settings['show_in_ucp'] ?? 'disabled', FALSE, array(
|
||||
'options' => array(
|
||||
'always' => 'login_handler_show_in_ucp_always',
|
||||
'loggedin' => 'login_handler_show_in_ucp_loggedin',
|
||||
@@ -180,37 +201,37 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
if ( $forceNameHandler = static::handlerHasForceSync( 'name', $this ) )
|
||||
{
|
||||
$nameChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
}
|
||||
$emailChangesDisabled = array();
|
||||
if ( $forceEmailHandler = static::handlerHasForceSync( 'email', $this ) )
|
||||
{
|
||||
$emailChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
}
|
||||
|
||||
$return['update_name_changes_inc_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes_inc_optional', isset( $this->settings['update_name_changes'] ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
$return['update_name_changes_inc_optional'] = new Radio( 'login_update_name_changes_inc_optional', $this->settings['update_name_changes'] ?? 'disabled', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $nameChangesDisabled ), NULL, NULL, NULL, 'login_update_name_changes_inc_optional' );
|
||||
$return['update_name_changes_no_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes_no_optional', ( isset( $this->settings['update_name_changes'] ) and $this->settings['update_name_changes'] != 'optional' ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
$return['update_name_changes_no_optional'] = new Radio( 'login_update_name_changes_no_optional', ( isset( $this->settings['update_name_changes'] ) and $this->settings['update_name_changes'] != 'optional' ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $nameChangesDisabled ), NULL, NULL, NULL, 'login_update_name_changes_no_optional' );
|
||||
$return['update_email_changes_inc_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes_inc_optional', isset( $this->settings['update_email_changes'] ) ? $this->settings['update_email_changes'] : 'force', FALSE, array( 'options' => array(
|
||||
$return['update_email_changes_inc_optional'] = new Radio( 'login_update_email_changes_inc_optional', $this->settings['update_email_changes'] ?? 'force', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $emailChangesDisabled ), NULL, NULL, NULL, 'login_update_email_changes_inc_optional' );
|
||||
$return['update_email_changes_no_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes_no_optional', ( isset( $this->settings['update_email_changes'] ) and $this->settings['update_email_changes'] != 'optional' ) ? $this->settings['update_email_changes'] : 'force', FALSE, array( 'options' => array(
|
||||
$return['update_email_changes_no_optional'] = new Radio( 'login_update_email_changes_no_optional', ( isset( $this->settings['update_email_changes'] ) and $this->settings['update_email_changes'] != 'optional' ) ? $this->settings['update_email_changes'] : 'force', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $emailChangesDisabled ), NULL, NULL, NULL, 'login_update_email_changes_no_optional' );
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_name_changes_inc_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_name_changes_no_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_email_changes_inc_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_email_changes_no_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
Member::loggedIn()->language()->words['login_update_name_changes_inc_optional'] = Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
Member::loggedIn()->language()->words['login_update_name_changes_no_optional'] = Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
Member::loggedIn()->language()->words['login_update_email_changes_inc_optional'] = Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
Member::loggedIn()->language()->words['login_update_email_changes_no_optional'] = Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -221,7 +242,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
public function acpFormSave( array &$values ): array
|
||||
{
|
||||
$_values = $values;
|
||||
|
||||
@@ -252,9 +273,9 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function testSettings()
|
||||
public function testSettings(): bool
|
||||
{
|
||||
$select = array( $this->settings['db_col_pass'] );
|
||||
|
||||
@@ -272,7 +293,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
$result = $this->_externalDb()->select( implode( ',', $select ), $this->settings['db_table'], ( isset( $this->settings['db_extra'] ) AND $this->settings['db_extra'] != '' ) ? array( $this->settings['db_extra'] ) : NULL )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
// It's possible that no users exist, which is fine
|
||||
}
|
||||
@@ -285,39 +306,36 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
|
||||
public function authenticateUsernamePassword( Login $login, string $usernameOrEmail, object $password ): Member
|
||||
{
|
||||
/* Fetch result */
|
||||
try
|
||||
{
|
||||
if( !$usernameOrEmail )
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
throw new UnderflowException;
|
||||
}
|
||||
|
||||
$result = $this->_getRowFromExternalDb( $usernameOrEmail );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
catch ( DbException $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$member = new \IPS\Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
}
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::NO_ACCOUNT, NULL, $member );
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE ), Exception::NO_ACCOUNT, NULL, $member );
|
||||
}
|
||||
|
||||
/* Get a local account if one exists */
|
||||
@@ -328,23 +346,23 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$link = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $result[ $this->settings['db_col_id'] ] ) )->first();
|
||||
$member = \IPS\Member::load( $link['token_member'] );
|
||||
$link = Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $result[ $this->settings['db_col_id'] ] ) )->first();
|
||||
$member = Member::load( $link['token_member'] );
|
||||
|
||||
/* If the user never finished the linking process, or the account has been deleted, discard this access token */
|
||||
if ( !$link['token_linked'] or !$member->member_id )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $link['token_member'] ) );
|
||||
Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $link['token_member'] ) );
|
||||
$member = NULL;
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $name )
|
||||
{
|
||||
$_member = \IPS\Member::load( $name, 'name' );
|
||||
$_member = Member::load( $name, 'name' );
|
||||
if ( $_member->member_id )
|
||||
{
|
||||
$member = $_member;
|
||||
@@ -352,7 +370,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
if ( $email )
|
||||
{
|
||||
$_member = \IPS\Member::load( $email, 'email' );
|
||||
$_member = Member::load( $email, 'email' );
|
||||
if ( $_member->member_id )
|
||||
{
|
||||
$member = $_member;
|
||||
@@ -363,7 +381,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/* Verify password */
|
||||
if( !$this->_passwordIsValid( $result, $password ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE ), Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
|
||||
/* Create account if we don't have one */
|
||||
@@ -375,9 +393,9 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( $login->type === \IPS\Login::LOGIN_UCP )
|
||||
if ( $login->type === Login::LOGIN_UCP )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception = new Exception( 'generic_error', Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception->handler = $this;
|
||||
$exception->member = $login->reauthenticateAs;
|
||||
throw $exception;
|
||||
@@ -385,7 +403,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
|
||||
$member = $this->createAccount( $name, $email );
|
||||
|
||||
\IPS\Db::i()->insert( 'core_login_links', array(
|
||||
Db::i()->insert( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $member->member_id,
|
||||
'token_identifier' => $result[ $this->settings['db_col_id'] ],
|
||||
@@ -414,23 +432,23 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Login\Exception $exception )
|
||||
catch ( Exception $exception )
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
if ( $exception->getCode() === Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
{
|
||||
try
|
||||
{
|
||||
$identifier = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
$identifier = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
|
||||
if( $identifier != $result[ $this->settings['db_col_id'] ] )
|
||||
{
|
||||
$exception->setCode( \IPS\Login\Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
$exception->setCode( Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_login_links', array(
|
||||
Db::i()->insert( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $exception->member->member_id,
|
||||
'token_identifier' => $result[ $this->settings['db_col_id'] ],
|
||||
@@ -451,71 +469,41 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticatePasswordForMember( \IPS\Member $member, $password )
|
||||
public function authenticatePasswordForMember( Member $member, object $password ): bool
|
||||
{
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
try
|
||||
{
|
||||
try
|
||||
$result = $this->_getRowFromExternalDb( $member->email );
|
||||
|
||||
if( $this->_passwordIsValid( $result, $password ) )
|
||||
{
|
||||
$result = $this->_getRowFromExternalDb( $member->email );
|
||||
|
||||
if( $this->_passwordIsValid( $result, $password ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
try
|
||||
{
|
||||
$result = $this->_getRowFromExternalDb( $member->name );
|
||||
|
||||
if( $this->_passwordIsValid( $result, $password ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
catch ( \Exception $e ) { }
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row from external database
|
||||
*
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @return array
|
||||
* @throws \UnderflowException
|
||||
* @throws \IPS\Db\Exception
|
||||
* @throws UnderflowException
|
||||
* @throws DbException
|
||||
*/
|
||||
public function _getRowFromExternalDb( $usernameOrEmail )
|
||||
public function _getRowFromExternalDb( string $usernameOrEmail ): array
|
||||
{
|
||||
$where = array();
|
||||
|
||||
/* Build where clause */
|
||||
switch ( $this->authType() )
|
||||
{
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME:
|
||||
$where[] = array( "{$this->settings['db_col_user']}=?", $usernameOrEmail );
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
$where[] = array( "{$this->settings['db_col_email']}=?", $usernameOrEmail );
|
||||
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']}=?)", $usernameOrEmail, $usernameOrEmail );
|
||||
break;
|
||||
|
||||
}
|
||||
$where[] = array( "{$this->settings['db_col_email']}=?", $usernameOrEmail );
|
||||
|
||||
if ( $this->settings['db_extra'] )
|
||||
{
|
||||
$where[] = array( $this->settings['db_extra'] );
|
||||
@@ -526,13 +514,14 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
|
||||
/* !Other Login Handler Methods */
|
||||
|
||||
|
||||
/**
|
||||
* Can this handler process a login for a member?
|
||||
* Can this handler process a login for a member?
|
||||
*
|
||||
* @return bool
|
||||
* @param Member $member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
public function canProcess( Member $member ): bool
|
||||
{
|
||||
if ( $this->settings['db_col_id'] )
|
||||
{
|
||||
@@ -540,24 +529,25 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this->authTypes & \IPS\Login::AUTH_TYPE_USERNAME and $member->name and $this->usernameIsInUse( $member->name ) )
|
||||
if ( $this->authTypes & 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 ) )
|
||||
if ( $this->authTypes & Login::AUTH_TYPE_EMAIL and $member->email and $this->emailIsInUse( $member->email ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Can this handler process a password change for a member?
|
||||
* Can this handler process a password change for a member?
|
||||
*
|
||||
* @return bool
|
||||
* @param Member $member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangePassword( \IPS\Member $member )
|
||||
public function canChangePassword( Member $member ): bool
|
||||
{
|
||||
if ( !isset( $this->settings['sync_password_changes'] ) or $this->settings['sync_password_changes'] )
|
||||
{
|
||||
@@ -571,9 +561,9 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canSyncPassword()
|
||||
public function canSyncPassword(): bool
|
||||
{
|
||||
return (bool) ( isset( $this->settings['sync_password_changes'] ) AND $this->settings['sync_password_changes'] );
|
||||
return ( isset( $this->settings['sync_password_changes'] ) AND $this->settings['sync_password_changes'] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -581,10 +571,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
* 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
|
||||
* @param 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 )
|
||||
public function emailIsInUse( string $email, Member $exclude=NULL ): ?bool
|
||||
{
|
||||
$where = array();
|
||||
$where[] = array( "{$this->settings['db_col_email']}=?", $email );
|
||||
@@ -595,10 +585,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
$where[] = array( "{$this->settings['db_col_id']}<>?", $linkedId );
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -611,11 +601,11 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
$this->_externalDb()->select( $this->settings['db_col_email'], $this->settings['db_table'], $where )->first();
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
catch ( DbException $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -626,10 +616,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @param \IPS\Member|NULL $exclude Member to exclude
|
||||
* @param Member|NULL $exclude Member to exclude
|
||||
* @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, \IPS\Member $exclude=NULL )
|
||||
public function usernameIsInUse( string $username, Member $exclude=NULL ): ?bool
|
||||
{
|
||||
$where = array();
|
||||
$where[] = array( "{$this->settings['db_col_user']}=?", $username );
|
||||
@@ -640,10 +630,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
$where[] = array( "{$this->settings['db_col_id']}<>?", $linkedId );
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -656,11 +646,11 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
$result = $this->_externalDb()->select( $this->settings['db_col_user'], $this->settings['db_table'], $where )->first();
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
catch ( DbException $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -669,13 +659,13 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param Member $member The member
|
||||
* @param string $oldEmail Old Email Address
|
||||
* @param string $newEmail New Email Address
|
||||
* @return void
|
||||
* @throws \IPS\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function changeEmail( \IPS\Member $member, $oldEmail, $newEmail )
|
||||
public function changeEmail( Member $member, string $oldEmail, string $newEmail ) : void
|
||||
{
|
||||
if ( $this->settings['db_col_email'] and ( !isset( $this->settings['sync_email_changes'] ) or $this->settings['sync_email_changes'] ) )
|
||||
{
|
||||
@@ -684,10 +674,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$where = array( "{$this->settings['db_col_id']}=?", $linkedId );
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
$this->_externalDb()->update( $this->settings['db_table'], array( $this->settings['db_col_email'] => $newEmail ), $where );
|
||||
}
|
||||
@@ -696,12 +686,12 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param Member $member The member
|
||||
* @param string $newPassword New Password, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return void
|
||||
* @throws \IPS\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
public function changePassword( Member $member, string $newPassword ) : void
|
||||
{
|
||||
if ( !isset( $this->settings['sync_password_changes'] ) or $this->settings['sync_password_changes'] )
|
||||
{
|
||||
@@ -709,10 +699,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$where = array( "{$this->settings['db_col_id']}=?", $linkedId );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -722,15 +712,15 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
$where = '1=0';
|
||||
switch ( $this->authTypes )
|
||||
{
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME:
|
||||
case Login::AUTH_TYPE_USERNAME:
|
||||
$where = array( "{$this->settings['db_col_user']}=?", $member->name );
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
case Login::AUTH_TYPE_EMAIL:
|
||||
$where = array( "{$this->settings['db_col_email']}=?", $member->email );
|
||||
break;
|
||||
|
||||
case \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL:
|
||||
case Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL:
|
||||
$where = array( "{$this->settings['db_col_email']}=? OR {$this->settings['db_col_user']}=?", $member->email, $member->name );
|
||||
break;
|
||||
}
|
||||
@@ -743,13 +733,13 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
* @throws \IPS\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
public function changeUsername( Member $member, string $oldUsername, string $newUsername ) : void
|
||||
{
|
||||
if ( $this->settings['db_col_user'] and ( !isset( $this->settings['sync_name_changes'] ) or $this->settings['sync_name_changes'] ) )
|
||||
{
|
||||
@@ -758,10 +748,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$where = array( "{$this->settings['db_col_id']}=?", $linkedId );
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
$this->_externalDb()->update( $this->settings['db_table'], array( $this->settings['db_col_user'] => $newUsername ), $where );
|
||||
}
|
||||
@@ -770,21 +760,21 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Forgot Password URL
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forgotPasswordUrl()
|
||||
public function forgotPasswordUrl(): ?Url
|
||||
{
|
||||
return ( isset( $this->settings['forgot_password_url'] ) and $this->settings['forgot_password_url'] ) ? \IPS\Http\Url::external( $this->settings['forgot_password_url'] ) : NULL;
|
||||
return ( isset( $this->settings['forgot_password_url'] ) and $this->settings['forgot_password_url'] ) ? Url::external( $this->settings['forgot_password_url'] ) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force Password Reset URL
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param \IPS\Http\Url|NULL $ref Referrer
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @param Member $member The member
|
||||
* @param Url|NULL $ref Referrer
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forcePasswordResetUrl( \IPS\Member $member, ?\IPS\Http\Url $ref = NULL ): ?\IPS\Http\Url
|
||||
public function forcePasswordResetUrl( Member $member, ?Url $ref = NULL ): ?Url
|
||||
{
|
||||
return $member->passwordResetForced( $ref );
|
||||
}
|
||||
@@ -793,13 +783,13 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['db_col_user'] ) and $this->settings['db_col_user'] )
|
||||
{
|
||||
@@ -809,25 +799,17 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
if ( $this->settings['db_col_id'] )
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$result = $this->_externalDb()->select( '*', $this->settings['db_table'], array( "{$this->settings['db_col_id']}=?", $linkedId ) )->first();;
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$result = $this->_externalDb()->select( '*', $this->settings['db_table'], array( "{$this->settings['db_col_id']}=?", $linkedId ) )->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$result = $this->_getRowFromExternalDb( $member->email );
|
||||
}
|
||||
|
||||
if ( !$result and $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
$result = $this->_getRowFromExternalDb( $member->name );
|
||||
}
|
||||
$result = $this->_getRowFromExternalDb( $member->email );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( $result )
|
||||
@@ -836,7 +818,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,13 +829,13 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
* Get user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userEmail( \IPS\Member $member )
|
||||
public function userEmail( Member $member ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['db_col_email'] ) and $this->settings['db_col_email'] )
|
||||
{
|
||||
@@ -863,25 +845,17 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
if ( $this->settings['db_col_id'] )
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$result = $this->_externalDb()->select( '*', $this->settings['db_table'], array( "{$this->settings['db_col_id']}=?", $linkedId ) )->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$result = $this->_getRowFromExternalDb( $member->email );
|
||||
}
|
||||
|
||||
if ( !$result and $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
$result = $this->_getRowFromExternalDb( $member->name );
|
||||
}
|
||||
$result = $this->_getRowFromExternalDb( $member->email );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( $result )
|
||||
@@ -890,7 +864,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,11 +874,11 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -926,7 +900,7 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSyncOptions()
|
||||
public function hasSyncOptions(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -936,22 +910,22 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
/**
|
||||
* Get DB Connection
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Db\Exception
|
||||
* @return Db
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function _externalDb()
|
||||
protected function _externalDb(): Db
|
||||
{
|
||||
return \IPS\Db::i( 'external_login_' . $this->id, $this->settings );
|
||||
return Db::i( 'external_login_' . $this->id, $this->settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Password is valid
|
||||
*
|
||||
* @param array $row The member row
|
||||
* @param object $providedPassword The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param array $row The member row
|
||||
* @param object $providedPassword The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
protected function _passwordIsValid( $row, $providedPassword )
|
||||
protected function _passwordIsValid( array $row, object $providedPassword ): bool
|
||||
{
|
||||
switch ( $this->settings['db_encryption'] )
|
||||
{
|
||||
@@ -963,29 +937,24 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
return @eval( $this->settings['db_encryption_validate'] );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( \Exception|Throwable $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'external_login' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
catch ( \Throwable $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'external_login' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'external_login' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
default:
|
||||
return \IPS\Login::compareHashes( $row[ $this->settings['db_col_pass'] ], $this->_encryptedPassword( $providedPassword ) );
|
||||
return Login::compareHashes( $row[ $this->settings['db_col_pass'] ], $this->_encryptedPassword( $providedPassword ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypted password
|
||||
*
|
||||
* @param object $providedPassword The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param object|string $providedPassword The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return string
|
||||
*/
|
||||
protected function _encryptedPassword( $providedPassword )
|
||||
protected function _encryptedPassword( object|string $providedPassword ): string
|
||||
{
|
||||
$providedPassword = (string) $providedPassword;
|
||||
|
||||
@@ -1005,15 +974,10 @@ class _ExternalDatabase extends \IPS\Login\Handler
|
||||
{
|
||||
return @eval( $this->settings['db_encryption_hash'] );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( \Exception|Throwable $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'external_login' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
catch ( \Throwable $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'external_login' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'external_login' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
+238
-229
File diff suppressed because it is too large.
Load diff
@@ -11,16 +11,20 @@
|
||||
namespace IPS\Login\Handler\LDAP;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP Exception
|
||||
*/
|
||||
class _Exception extends \RuntimeException
|
||||
class Exception extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
+174
-160
@@ -11,21 +11,47 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Login\Handler\LDAP\Exception as LdapException;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use LDAP\Connection;
|
||||
use LDAP\ResultEntry;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use UnderflowException;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function intval;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP Database Login Handler
|
||||
*/
|
||||
class _LDAP extends \IPS\Login\Handler
|
||||
class LDAP extends Handler
|
||||
{
|
||||
/**
|
||||
* @brief Can we have multiple instances of this handler?
|
||||
*/
|
||||
public static $allowMultiple = TRUE;
|
||||
public static bool $allowMultiple = TRUE;
|
||||
|
||||
use UsernamePasswordHandler;
|
||||
|
||||
@@ -36,7 +62,7 @@ class _LDAP extends \IPS\Login\Handler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Ldap';
|
||||
}
|
||||
@@ -44,66 +70,65 @@ class _LDAP extends \IPS\Login\Handler
|
||||
/**
|
||||
* 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_methods.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$id = $this->id ?: 'new';
|
||||
|
||||
$return = array(
|
||||
'ldap_header',
|
||||
'server_protocol' => new \IPS\Helpers\Form\Radio( 'ldap_server_protocol', isset( $this->settings['server_protocol'] ) ? $this->settings['server_protocol'] : 3, TRUE, array( 'options' => array( 3 => 'V3', 2 => 'V2' ) ) ),
|
||||
'server_host' => new \IPS\Helpers\Form\Text( 'ldap_server_host', isset( $this->settings['server_host'] ) ? $this->settings['server_host'] : NULL, TRUE, array( 'placeholder' => 'ldap.example.com' ) ),
|
||||
'server_port' => new \IPS\Helpers\Form\Number( 'ldap_server_port', isset( $this->settings['server_port'] ) ? $this->settings['server_port'] : 389, TRUE ),
|
||||
'server_user' => new \IPS\Helpers\Form\Text( 'ldap_server_user', isset( $this->settings['server_user'] ) ? $this->settings['server_user'] : NULL ),
|
||||
'server_pass' => new \IPS\Helpers\Form\Text( 'ldap_server_pass', isset( $this->settings['server_pass'] ) ? $this->settings['server_pass'] : NULL ),
|
||||
'opt_referrals' => new \IPS\Helpers\Form\YesNo( 'ldap_opt_referrals', isset( $this->settings['opt_referrals'] ) ? $this->settings['opt_referrals'] : TRUE, TRUE ),
|
||||
'server_protocol' => new Radio( 'ldap_server_protocol', $this->settings['server_protocol'] ?? 3, TRUE, array( 'options' => array( 3 => 'V3', 2 => 'V2' ) ) ),
|
||||
'server_host' => new Text( 'ldap_server_host', $this->settings['server_host'] ?? NULL, TRUE, array( 'placeholder' => 'ldap.example.com' ) ),
|
||||
'server_port' => new Number( 'ldap_server_port', $this->settings['server_port'] ?? 389, TRUE ),
|
||||
'server_user' => new Text( 'ldap_server_user', $this->settings['server_user'] ?? NULL ),
|
||||
'server_pass' => new Text( 'ldap_server_pass', $this->settings['server_pass'] ?? NULL ),
|
||||
'opt_referrals' => new YesNo( 'ldap_opt_referrals', $this->settings['opt_referrals'] ?? TRUE, TRUE ),
|
||||
'ldap_directory',
|
||||
'base_dn' => new \IPS\Helpers\Form\Text( 'ldap_base_dn', isset( $this->settings['base_dn'] ) ? $this->settings['base_dn'] : NULL, TRUE, array( 'placeholder' => 'dc=example,dc=com' ) ),
|
||||
'uid_field' => new \IPS\Helpers\Form\Text( 'ldap_uid_field', isset( $this->settings['uid_field'] ) ? $this->settings['uid_field'] : 'uid', TRUE ),
|
||||
'un_suffix' => new \IPS\Helpers\Form\Text( 'ldap_un_suffix', isset( $this->settings['un_suffix'] ) ? $this->settings['un_suffix'] : NULL, FALSE, array( 'placeholder' => '@example.com' ) ),
|
||||
'name_field' => new \IPS\Helpers\Form\Text( 'ldap_name_field', $this->_nameField() ?: 'cn' ),
|
||||
'email_field' => new \IPS\Helpers\Form\Text( 'ldap_email_field', isset( $this->settings['email_field'] ) ? $this->settings['email_field'] : 'mail' ),
|
||||
'filter' => new \IPS\Helpers\Form\Text( 'ldap_filter', isset( $this->settings['filter'] ) ? $this->settings['filter'] : NULL, FALSE, array( 'placeholder' => 'ou=your_department' ) ),
|
||||
'base_dn' => new Text( 'ldap_base_dn', $this->settings['base_dn'] ?? NULL, TRUE, array( 'placeholder' => 'dc=example,dc=com' ) ),
|
||||
'uid_field' => new Text( 'ldap_uid_field', $this->settings['uid_field'] ?? 'uid', TRUE ),
|
||||
'un_suffix' => new Text( 'ldap_un_suffix', $this->settings['un_suffix'] ?? NULL, FALSE, array( 'placeholder' => '@example.com' ) ),
|
||||
'name_field' => new Text( 'ldap_name_field', $this->_nameField() ?: 'cn' ),
|
||||
'email_field' => new Text( 'ldap_email_field', $this->settings['email_field'] ?? 'mail' ),
|
||||
'filter' => new Text( 'ldap_filter', $this->settings['filter'] ?? NULL, FALSE, array( 'placeholder' => 'ou=your_department' ) ),
|
||||
'login_settings',
|
||||
'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', isset( $this->settings['auth_types'] ) ? $this->settings['auth_types'] : ( \IPS\Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
), 'toggles' => array( \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => array( 'form_' . $id . '_login_auth_types_warning' ), \IPS\Login::AUTH_TYPE_USERNAME => array( 'form_' . $id . '_login_auth_types_warning' ) ) ) ),
|
||||
'pw_required' => new \IPS\Helpers\Form\YesNo( 'ldap_pw_required', isset( $this->settings['pw_required'] ) ? $this->settings['pw_required'] : TRUE ),
|
||||
'auth_types' => new Select( 'login_auth_types', $this->settings['auth_types'] ?? ( Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
Login::AUTH_TYPE_USERNAME => 'username',
|
||||
), 'toggles' => array( Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => array( 'form_' . $id . '_login_auth_types_warning' ), Login::AUTH_TYPE_USERNAME => array( 'form_' . $id . '_login_auth_types_warning' ) ) ) ),
|
||||
'pw_required' => new YesNo( 'ldap_pw_required', $this->settings['pw_required'] ?? TRUE ),
|
||||
);
|
||||
if ( \IPS\Settings::i()->allow_forgot_password == 'normal' or \IPS\Settings::i()->allow_forgot_password == 'handler' )
|
||||
if ( Settings::i()->allow_forgot_password == 'normal' or Settings::i()->allow_forgot_password == 'handler' )
|
||||
{
|
||||
$return['forgot_password_url'] = new \IPS\Helpers\Form\Url( 'handler_forgot_password_url', isset( $this->settings['forgot_password_url'] ) ? $this->settings['forgot_password_url'] : NULL );
|
||||
\IPS\Member::loggedIn()->language()->words['handler_forgot_password_url_desc'] = \IPS\Member::loggedIn()->language()->addToStack( \IPS\Settings::i()->allow_forgot_password == 'normal' ? 'handler_forgot_password_url_desc_normal' : 'handler_forgot_password_url_deschandler' );
|
||||
$return['forgot_password_url'] = new \IPS\Helpers\Form\Url( 'handler_forgot_password_url', $this->settings['forgot_password_url'] ?? NULL );
|
||||
Member::loggedIn()->language()->words['handler_forgot_password_url_desc'] = Member::loggedIn()->language()->addToStack( Settings::i()->allow_forgot_password == 'normal' ? 'handler_forgot_password_url_desc_normal' : 'handler_forgot_password_url_deschandler' );
|
||||
}
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
$return['sync_name_changes'] = new \IPS\Helpers\Form\Radio( 'login_sync_name_changes', isset( $this->settings['sync_name_changes'] ) ? $this->settings['sync_name_changes'] : 1, FALSE, array( 'options' => array(
|
||||
$return['sync_name_changes'] = new Radio( 'login_sync_name_changes', $this->settings['sync_name_changes'] ?? 1, FALSE, array( 'options' => array(
|
||||
1 => 'login_sync_changes_yes',
|
||||
0 => 'login_sync_changes_no',
|
||||
) ) );
|
||||
if ( \IPS\Settings::i()->allow_email_changes == 'normal' )
|
||||
if ( Settings::i()->allow_email_changes == 'normal' )
|
||||
{
|
||||
$return['sync_email_changes'] = new \IPS\Helpers\Form\Radio( 'login_sync_email_changes', isset( $this->settings['sync_email_changes'] ) ? $this->settings['sync_email_changes'] : 1, FALSE, array( 'options' => array(
|
||||
$return['sync_email_changes'] = new Radio( 'login_sync_email_changes', $this->settings['sync_email_changes'] ?? 1, FALSE, array( 'options' => array(
|
||||
1 => 'login_sync_changes_yes',
|
||||
0 => 'login_sync_changes_no',
|
||||
) ) );
|
||||
}
|
||||
if ( \IPS\Settings::i()->allow_password_changes == 'normal' )
|
||||
if ( Settings::i()->allow_password_changes == 'normal' )
|
||||
{
|
||||
$return['sync_password_changes'] = new \IPS\Helpers\Form\Radio( 'login_sync_password_changes', isset( $this->settings['sync_password_changes'] ) ? $this->settings['sync_password_changes'] : 1, FALSE, array( 'options' => array(
|
||||
$return['sync_password_changes'] = new Radio( 'login_sync_password_changes', $this->settings['sync_password_changes'] ?? 1, FALSE, array( 'options' => array(
|
||||
1 => 'login_sync_changes_yes',
|
||||
0 => 'login_sync_password_changes_no',
|
||||
) ) );
|
||||
}
|
||||
|
||||
$return['show_in_ucp'] = new \IPS\Helpers\Form\Radio( 'login_handler_show_in_ucp', isset( $this->settings['show_in_ucp'] ) ? $this->settings['show_in_ucp'] : 'disabled', FALSE, array(
|
||||
$return['show_in_ucp'] = new Radio( 'login_handler_show_in_ucp', $this->settings['show_in_ucp'] ?? 'disabled', FALSE, array(
|
||||
'options' => array(
|
||||
'always' => 'login_handler_show_in_ucp_always',
|
||||
'loggedin' => 'login_handler_show_in_ucp_loggedin',
|
||||
@@ -120,37 +145,37 @@ class _LDAP extends \IPS\Login\Handler
|
||||
if ( $forceNameHandler = static::handlerHasForceSync( 'name', $this ) )
|
||||
{
|
||||
$nameChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
}
|
||||
$emailChangesDisabled = array();
|
||||
if ( $forceEmailHandler = static::handlerHasForceSync( 'email', $this ) )
|
||||
{
|
||||
$emailChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
}
|
||||
|
||||
$return['update_name_changes_inc_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes_inc_optional', isset( $this->settings['update_name_changes'] ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
$return['update_name_changes_inc_optional'] = new Radio( 'login_update_name_changes_inc_optional', $this->settings['update_name_changes'] ?? 'disabled', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $nameChangesDisabled ), NULL, NULL, NULL, 'login_update_name_changes_inc_optional' );
|
||||
$return['update_name_changes_no_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes_no_optional', ( isset( $this->settings['update_name_changes'] ) and $this->settings['update_name_changes'] != 'optional' ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
$return['update_name_changes_no_optional'] = new Radio( 'login_update_name_changes_no_optional', ( isset( $this->settings['update_name_changes'] ) and $this->settings['update_name_changes'] != 'optional' ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $nameChangesDisabled ), NULL, NULL, NULL, 'login_update_name_changes_no_optional' );
|
||||
$return['update_email_changes_inc_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes_inc_optional', isset( $this->settings['update_email_changes'] ) ? $this->settings['update_email_changes'] : 'force', FALSE, array( 'options' => array(
|
||||
$return['update_email_changes_inc_optional'] = new Radio( 'login_update_email_changes_inc_optional', $this->settings['update_email_changes'] ?? 'force', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $emailChangesDisabled ), NULL, NULL, NULL, 'login_update_email_changes_inc_optional' );
|
||||
$return['update_email_changes_no_optional'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes_no_optional', ( isset( $this->settings['update_email_changes'] ) and $this->settings['update_email_changes'] != 'optional' ) ? $this->settings['update_email_changes'] : 'force', FALSE, array( 'options' => array(
|
||||
$return['update_email_changes_no_optional'] = new Radio( 'login_update_email_changes_no_optional', ( isset( $this->settings['update_email_changes'] ) and $this->settings['update_email_changes'] != 'optional' ) ? $this->settings['update_email_changes'] : 'force', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
), 'disabled' => $emailChangesDisabled ), NULL, NULL, NULL, 'login_update_email_changes_no_optional' );
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_name_changes_inc_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_name_changes_no_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_email_changes_inc_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_email_changes_no_optional'] = \IPS\Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
Member::loggedIn()->language()->words['login_update_name_changes_inc_optional'] = Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
Member::loggedIn()->language()->words['login_update_name_changes_no_optional'] = Member::loggedIn()->language()->addToStack('login_update_name_changes');
|
||||
Member::loggedIn()->language()->words['login_update_email_changes_inc_optional'] = Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
Member::loggedIn()->language()->words['login_update_email_changes_no_optional'] = Member::loggedIn()->language()->addToStack('login_update_email_changes');
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -161,7 +186,7 @@ class _LDAP extends \IPS\Login\Handler
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
public function acpFormSave( array &$values ): array
|
||||
{
|
||||
$_values = $values;
|
||||
|
||||
@@ -190,13 +215,13 @@ class _LDAP extends \IPS\Login\Handler
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function testSettings()
|
||||
public function testSettings(): bool
|
||||
{
|
||||
if ( !\extension_loaded('ldap') )
|
||||
if ( !extension_loaded('ldap') )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'login_ldap_err' ) );
|
||||
throw new InvalidArgumentException( Member::loggedIn()->language()->addToStack( 'login_ldap_err' ) );
|
||||
}
|
||||
|
||||
try
|
||||
@@ -205,7 +230,7 @@ class _LDAP extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( LDAP\Exception $e )
|
||||
{
|
||||
throw new \InvalidArgumentException( $e->getMessage() ?: \IPS\Member::loggedIn()->language()->addToStack('login_ldap_err_connect' ) );
|
||||
throw new InvalidArgumentException( $e->getMessage() ?: Member::loggedIn()->language()->addToStack('login_ldap_err_connect' ) );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -216,13 +241,13 @@ class _LDAP extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
|
||||
public function authenticateUsernamePassword( Login $login, string $usernameOrEmail, object $password ): Member
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -231,16 +256,7 @@ class _LDAP extends \IPS\Login\Handler
|
||||
if( $usernameOrEmail )
|
||||
{
|
||||
/* Try email address */
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$result = $this->_getUserWithFilter( $this->settings['email_field'] . '=' . ldap_escape( $usernameOrEmail, NULL, LDAP_ESCAPE_FILTER ) );
|
||||
}
|
||||
|
||||
/* Try username */
|
||||
if ( !$result and $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
$result = $this->_getUserWithFilter( $this->_nameField() . '=' . ldap_escape( $usernameOrEmail . $this->settings['un_suffix'], NULL, LDAP_ESCAPE_FILTER ) );
|
||||
}
|
||||
$result = $this->_getUserWithFilter( $this->settings['email_field'] . '=' . ldap_escape( $usernameOrEmail, NULL, LDAP_ESCAPE_FILTER ) );
|
||||
}
|
||||
|
||||
/* Don't have anything? */
|
||||
@@ -248,13 +264,10 @@ class _LDAP extends \IPS\Login\Handler
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$member = new \IPS\Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
}
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::NO_ACCOUNT, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE ), Exception::NO_ACCOUNT, NULL, $member );
|
||||
}
|
||||
|
||||
/* Get a local account if one exists */
|
||||
@@ -269,22 +282,22 @@ class _LDAP extends \IPS\Login\Handler
|
||||
$member = NULL;
|
||||
try
|
||||
{
|
||||
$link = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $attrs[ $this->settings['uid_field'] ][0] ) )->first();
|
||||
$member = \IPS\Member::load( $link['token_member'] );
|
||||
$link = Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $attrs[ $this->settings['uid_field'] ][0] ) )->first();
|
||||
$member = Member::load( $link['token_member'] );
|
||||
|
||||
/* If the user never finished the linking process, or the account has been deleted, discard this access token */
|
||||
if ( !$link['token_linked'] or !$member->member_id )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $link['token_member'] ) );
|
||||
Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $link['token_member'] ) );
|
||||
$member = NULL;
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
|
||||
/* Verify password */
|
||||
if ( !$this->_passwordIsValid( $result, $password ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE ), Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
|
||||
/* Create account if we don't have one */
|
||||
@@ -296,9 +309,9 @@ class _LDAP extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( $login->type === \IPS\Login::LOGIN_UCP )
|
||||
if ( $login->type === Login::LOGIN_UCP )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception = new Exception( 'generic_error', Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception->handler = $this;
|
||||
$exception->member = $login->reauthenticateAs;
|
||||
throw $exception;
|
||||
@@ -306,7 +319,7 @@ class _LDAP extends \IPS\Login\Handler
|
||||
|
||||
$member = $this->createAccount( $name, $email );
|
||||
|
||||
\IPS\Db::i()->insert( 'core_login_links', array(
|
||||
Db::i()->insert( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $member->member_id,
|
||||
'token_identifier' => $attrs[ $this->settings['uid_field'] ][0],
|
||||
@@ -335,23 +348,23 @@ class _LDAP extends \IPS\Login\Handler
|
||||
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Login\Exception $exception )
|
||||
catch ( Exception $exception )
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
if ( $exception->getCode() === Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
{
|
||||
try
|
||||
{
|
||||
$identifier = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
$identifier = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
|
||||
if( $identifier != $attrs[ $this->settings['uid_field'] ][0] )
|
||||
{
|
||||
$exception->setCode( \IPS\Login\Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
$exception->setCode( Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_login_links', array(
|
||||
Db::i()->insert( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $exception->member->member_id,
|
||||
'token_identifier' => $attrs[ $this->settings['uid_field'] ][0],
|
||||
@@ -366,48 +379,48 @@ class _LDAP extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( LDAP\Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'ldap' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'ldap' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticatePasswordForMember( \IPS\Member $member, $password )
|
||||
public function authenticatePasswordForMember( Member $member, object $password ): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
if ( $result = $this->_getUserWithFilter( $this->settings['uid_field'] . '=' . ldap_escape( $linkedId, NULL, LDAP_ESCAPE_FILTER ) ) )
|
||||
{
|
||||
return $this->_passwordIsValid( $result, $password );
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* !Utility Methods */
|
||||
|
||||
protected $_ldap;
|
||||
protected ?Connection $_ldap = null;
|
||||
|
||||
/**
|
||||
* Get LDAP Connection
|
||||
*
|
||||
* @return bool
|
||||
* @throws \IPS\Login\Handler\LDAP\Exception
|
||||
* @return Connection
|
||||
* @throws LdapException
|
||||
*/
|
||||
protected function _ldap()
|
||||
protected function _ldap() : Connection
|
||||
{
|
||||
if ( !$this->_ldap )
|
||||
{
|
||||
$this->_ldap = ldap_connect( $this->settings['server_host'], ( isset( $this->settings['server_port'] ) and $this->settings['server_port'] ) ? \intval( $this->settings['server_port'] ) : 389 );
|
||||
$this->_ldap = ldap_connect( $this->settings['server_host'], ( isset( $this->settings['server_port'] ) and $this->settings['server_port'] ) ? intval( $this->settings['server_port'] ) : 389 );
|
||||
if ( !$this->_ldap )
|
||||
{
|
||||
throw new LDAP\Exception;
|
||||
@@ -428,20 +441,20 @@ class _LDAP extends \IPS\Login\Handler
|
||||
/**
|
||||
* Get name field
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function _nameField()
|
||||
public function _nameField(): ?string
|
||||
{
|
||||
return isset( $this->settings['name_field'] ) ? $this->settings['name_field'] : ( isset( $this->settings['uid_field'] ) ? $this->settings['uid_field'] : NULL );
|
||||
return $this->settings['name_field'] ?? ( $this->settings['uid_field'] ?? NULL );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user
|
||||
*
|
||||
* @param string $filter Filter
|
||||
* @return resource|NULL
|
||||
* @param string $filter Filter
|
||||
* @return ResultEntry|null
|
||||
*/
|
||||
protected function _getUserWithFilter( $filter )
|
||||
protected function _getUserWithFilter( string $filter ) : ?ResultEntry
|
||||
{
|
||||
/* Add any additional filter */
|
||||
if ( $this->settings['filter'] )
|
||||
@@ -476,23 +489,24 @@ class _LDAP extends \IPS\Login\Handler
|
||||
/**
|
||||
* Password is valid
|
||||
*
|
||||
* @param resource $result The resource from LDAP
|
||||
* @param object $providedPassword The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param ResultEntry $result The resource from LDAP
|
||||
* @param object|string $providedPassword The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
protected function _passwordIsValid( $result, $providedPassword )
|
||||
protected function _passwordIsValid( ResultEntry $result, object|string $providedPassword ): bool
|
||||
{
|
||||
return (bool) @ldap_bind( $this->_ldap(), ldap_get_dn( $this->_ldap(), $result ), ( $this->settings['pw_required'] ? ( (string) $providedPassword ) : '' ) );
|
||||
return @ldap_bind( $this->_ldap(), ldap_get_dn( $this->_ldap(), $result ), ( $this->settings['pw_required'] ? ( (string) $providedPassword ) : '' ) );
|
||||
}
|
||||
|
||||
/* !Other Login Handler Methods */
|
||||
|
||||
/**
|
||||
* Can this handler process a password change for a member?
|
||||
* Can this handler process a password change for a member?
|
||||
*
|
||||
* @return bool
|
||||
* @param Member $member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangePassword( \IPS\Member $member )
|
||||
public function canChangePassword( Member $member ): bool
|
||||
{
|
||||
if ( !isset( $this->settings['sync_password_changes'] ) or $this->settings['sync_password_changes'] )
|
||||
{
|
||||
@@ -506,9 +520,9 @@ class _LDAP extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canSyncPassword()
|
||||
public function canSyncPassword(): bool
|
||||
{
|
||||
return (bool) ( isset( $this->settings['sync_password_changes'] ) AND $this->settings['sync_password_changes'] );
|
||||
return ( isset( $this->settings['sync_password_changes'] ) AND $this->settings['sync_password_changes'] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,10 +530,10 @@ class _LDAP extends \IPS\Login\Handler
|
||||
* 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
|
||||
* @param 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 )
|
||||
public function emailIsInUse( string $email, Member $exclude=NULL ): ?bool
|
||||
{
|
||||
if ( $this->settings['email_field'] )
|
||||
{
|
||||
@@ -529,14 +543,14 @@ class _LDAP extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
|
||||
if ( $attrs = @ldap_get_attributes( $this->_ldap(), $result ) and $attrs[ $this->settings['uid_field'] ][0] == $linkedId )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -551,10 +565,10 @@ class _LDAP extends \IPS\Login\Handler
|
||||
* Used when registering or changing an username to check the new one is available
|
||||
*
|
||||
* @param string $username Username
|
||||
* @param \IPS\Member|NULL $exclude Member to exclude
|
||||
* @param Member|NULL $exclude Member to exclude
|
||||
* @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, \IPS\Member $exclude=NULL )
|
||||
public function usernameIsInUse( string $username, Member $exclude=NULL ): ?bool
|
||||
{
|
||||
if ( $this->_nameField() )
|
||||
{
|
||||
@@ -564,14 +578,14 @@ class _LDAP extends \IPS\Login\Handler
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exclude->member_id ) )->first();
|
||||
|
||||
if ( $attrs = @ldap_get_attributes( $this->_ldap(), $result ) and $attrs[ $this->settings['uid_field'] ][0] == $linkedId )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -584,94 +598,94 @@ class _LDAP extends \IPS\Login\Handler
|
||||
/**
|
||||
* Change Email Address
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param 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 )
|
||||
public function changeEmail( Member $member, string $oldEmail, string $newEmail ) : void
|
||||
{
|
||||
if ( !isset( $this->settings['sync_email_changes'] ) or $this->settings['sync_email_changes'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
if ( $result = $this->_getUserWithFilter( $this->settings['uid_field'] . '=' . ldap_escape( $linkedId, NULL, LDAP_ESCAPE_FILTER ) ) )
|
||||
{
|
||||
if ( !@ldap_modify( $this->_ldap(), ldap_get_dn( $this->_ldap(), $result ), array( $this->settings['email_field'] => $newEmail ) ) )
|
||||
{
|
||||
$e = new LDAP\Exception( ldap_error( $this->_ldap() ), ldap_errno( $this->_ldap() ) );
|
||||
\IPS\Log::log( $e, 'ldap' );
|
||||
Log::log( $e, 'ldap' );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $newPassword New Password, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param string $newPassword New Password, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return void
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
public function changePassword( Member $member, string $newPassword ) : void
|
||||
{
|
||||
if ( !isset( $this->settings['sync_password_changes'] ) or $this->settings['sync_password_changes'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
if ( $result = $this->_getUserWithFilter( $this->settings['uid_field'] . '=' . ldap_escape( $linkedId, NULL, LDAP_ESCAPE_FILTER ) ) )
|
||||
{
|
||||
if ( !@ldap_modify( $this->_ldap(), ldap_get_dn( $this->_ldap(), $result ), array( 'userPassword' => "{SHA}" . base64_encode( pack( "H*", sha1( $newPassword ) ) ) ) ) )
|
||||
{
|
||||
$e = new LDAP\Exception( ldap_error( $this->_ldap() ), ldap_errno( $this->_ldap() ) );
|
||||
\IPS\Log::log( $e, 'ldap' );
|
||||
Log::log( $e, 'ldap' );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Username
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param Member $member The member
|
||||
* @param string $oldUsername Old Username
|
||||
* @param string $newUsername New Username
|
||||
* @return void
|
||||
*/
|
||||
public function changeUsername( \IPS\Member $member, $oldUsername, $newUsername )
|
||||
public function changeUsername( Member $member, string $oldUsername, string $newUsername ) : void
|
||||
{
|
||||
if ( !isset( $this->settings['sync_name_changes'] ) or $this->settings['sync_name_changes'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$linkedId = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
$linkedId = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) )->first();
|
||||
if ( $result = $this->_getUserWithFilter( $this->settings['uid_field'] . '=' . ldap_escape( $linkedId, NULL, LDAP_ESCAPE_FILTER ) ) )
|
||||
{
|
||||
if ( !@ldap_modify( $this->_ldap(), ldap_get_dn( $this->_ldap(), $result ), array( $this->_nameField() => $newUsername ) ) )
|
||||
{
|
||||
$e = new LDAP\Exception( ldap_error( $this->_ldap() ), ldap_errno( $this->_ldap() ) );
|
||||
\IPS\Log::log( $e, 'ldap' );
|
||||
Log::log( $e, 'ldap' );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e ) { }
|
||||
catch ( UnderflowException $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forgot Password URL
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forgotPasswordUrl()
|
||||
public function forgotPasswordUrl(): ?Url
|
||||
{
|
||||
return ( isset( $this->settings['forgot_password_url'] ) and $this->settings['forgot_password_url'] ) ? \IPS\Http\Url::external( $this->settings['forgot_password_url'] ) : NULL;
|
||||
return ( isset( $this->settings['forgot_password_url'] ) and $this->settings['forgot_password_url'] ) ? Url::external( $this->settings['forgot_password_url'] ) : NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -679,19 +693,19 @@ class _LDAP extends \IPS\Login\Handler
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( $nameField = $this->_nameField() )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( $result = $this->_getUserWithFilter( $this->settings['uid_field'] . '=' . ldap_escape( $link['token_identifier'], NULL, LDAP_ESCAPE_FILTER ) ) )
|
||||
@@ -702,12 +716,12 @@ class _LDAP extends \IPS\Login\Handler
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \RuntimeException;
|
||||
throw new RuntimeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,19 +732,19 @@ class _LDAP extends \IPS\Login\Handler
|
||||
* Get user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userEmail( \IPS\Member $member )
|
||||
public function userEmail( Member $member ): ?string
|
||||
{
|
||||
if ( $this->settings['email_field'] )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( $result = $this->_getUserWithFilter( $this->settings['uid_field'] . '=' . ldap_escape( $link['token_identifier'], NULL, LDAP_ESCAPE_FILTER ) ) )
|
||||
@@ -741,12 +755,12 @@ class _LDAP extends \IPS\Login\Handler
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \RuntimeException;
|
||||
throw new RuntimeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,11 +770,11 @@ class _LDAP extends \IPS\Login\Handler
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -782,7 +796,7 @@ class _LDAP extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSyncOptions()
|
||||
public function hasSyncOptions(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -12,38 +12,58 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Http\Response;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use UnderflowException;
|
||||
use function count;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract OAuth1 Login Handler
|
||||
*/
|
||||
abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
abstract class OAuth1 extends Handler
|
||||
{
|
||||
/* !Login Handler: Basics */
|
||||
|
||||
/**
|
||||
* 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_methods.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$return = array(
|
||||
array( 'login_handler_oauth_settings', \IPS\Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
|
||||
'consumer_key' => new \IPS\Helpers\Form\Text( 'oauth_consumer_key', ( isset( $this->settings['consumer_key'] ) ) ? $this->settings['consumer_key'] : '', TRUE ),
|
||||
'consumer_secret' => new \IPS\Helpers\Form\Text( 'oauth_consumer_secret', ( isset( $this->settings['consumer_secret'] ) ) ? $this->settings['consumer_secret'] : '', TRUE ),
|
||||
array( 'login_handler_oauth_settings', Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
|
||||
'consumer_key' => new Text( 'oauth_consumer_key', ( isset( $this->settings['consumer_key'] ) ) ? $this->settings['consumer_key'] : '', TRUE ),
|
||||
'consumer_secret' => new Text( 'oauth_consumer_secret', ( isset( $this->settings['consumer_secret'] ) ) ? $this->settings['consumer_secret'] : '', TRUE ),
|
||||
);
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
$return['show_in_ucp'] = new \IPS\Helpers\Form\Radio( 'login_handler_show_in_ucp', isset( $this->settings['show_in_ucp'] ) ? $this->settings['show_in_ucp'] : 'always', FALSE, array(
|
||||
$return['show_in_ucp'] = new Radio( 'login_handler_show_in_ucp', $this->settings['show_in_ucp'] ?? 'always', FALSE, array(
|
||||
'options' => array(
|
||||
'always' => 'login_handler_show_in_ucp_always',
|
||||
'loggedin' => 'login_handler_show_in_ucp_loggedin',
|
||||
@@ -55,9 +75,9 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
if ( $forceNameHandler = static::handlerHasForceSync( 'name', $this ) )
|
||||
{
|
||||
$nameChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
}
|
||||
$return['update_name_changes'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes', isset( $this->settings['update_name_changes'] ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
$return['update_name_changes'] = new Radio( 'login_update_name_changes', $this->settings['update_name_changes'] ?? 'disabled', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
@@ -67,9 +87,9 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
if ( $forceEmailHandler = static::handlerHasForceSync( 'email', $this ) )
|
||||
{
|
||||
$emailChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
}
|
||||
$return['update_email_changes'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes', isset( $this->settings['update_email_changes'] ) ? $this->settings['update_email_changes'] : 'optional', FALSE, array( 'options' => array(
|
||||
$return['update_email_changes'] = new Radio( 'login_update_email_changes', $this->settings['update_email_changes'] ?? 'optional', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
@@ -82,9 +102,9 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function testSettings()
|
||||
public function testSettings(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -92,7 +112,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth1_setup_error', FALSE, array( 'sprintf' => array( $e->getMessage() ) ) ) );
|
||||
throw new LogicException( Member::loggedIn()->language()->addToStack( 'oauth1_setup_error', FALSE, array( 'sprintf' => array( $e->getMessage() ) ) ) );
|
||||
}
|
||||
|
||||
try
|
||||
@@ -101,28 +121,30 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth1_setup_error', FALSE, array( 'sprintf' => array( (string) $response ) ) ) );
|
||||
throw new LogicException( Member::loggedIn()->language()->addToStack( 'oauth1_setup_error', FALSE, array( 'sprintf' => array( (string) $response ) ) ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* !Authentication */
|
||||
|
||||
use \IPS\Login\Handler\ButtonHandler;
|
||||
use ButtonHandler;
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @return Member|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticateButton( \IPS\Login $login )
|
||||
public function authenticateButton( Login $login ) : ?Member
|
||||
{
|
||||
if ( isset( \IPS\Request::i()->denied ) )
|
||||
if ( isset( Request::i()->denied ) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
elseif ( isset( \IPS\Request::i()->oauth_token ) )
|
||||
elseif ( isset( Request::i()->oauth_token ) )
|
||||
{
|
||||
return $this->_handleAuthorizationResponse( $login );
|
||||
}
|
||||
@@ -130,17 +152,19 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
{
|
||||
$this->_redirectToAuthorizationEndpoint( $login );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to Resource Owner Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param Login $login The login object
|
||||
* @return void
|
||||
*/
|
||||
protected function _redirectToAuthorizationEndpoint( \IPS\Login $login )
|
||||
protected function _redirectToAuthorizationEndpoint( Login $login ) : void
|
||||
{
|
||||
$callback = $this->redirectionEndpoint()->setQueryString( 'state' , $this->id . '-' . base64_encode( $login->url ) . '-' . \IPS\Session::i()->csrfKey . '-' . \IPS\Request::i()->ref );
|
||||
$callback = $this->redirectionEndpoint()->setQueryString( 'state' , $this->id . '-' . base64_encode( $login->url ) . '-' . Session::i()->csrfKey . '-' . Request::i()->ref );
|
||||
|
||||
try
|
||||
{
|
||||
@@ -148,31 +172,31 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'twitter' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'twitter' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( $this->authorizationEndpoint( $login )->setQueryString( 'oauth_token', $response['oauth_token'] ) );
|
||||
Output::i()->redirect( $this->authorizationEndpoint( $login )->setQueryString( 'oauth_token', $response['oauth_token'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization response
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _handleAuthorizationResponse( \IPS\Login $login )
|
||||
protected function _handleAuthorizationResponse( Login $login ): Member
|
||||
{
|
||||
/* Authenticate */
|
||||
try
|
||||
{
|
||||
$response = $this->_sendRequest( 'post', $this->accessTokenEndpoint(), array( 'oauth_verifier' => \IPS\Request::i()->oauth_verifier ), \IPS\Request::i()->oauth_token )->decodeQueryString('oauth_token');
|
||||
$response = $this->_sendRequest( 'post', $this->accessTokenEndpoint(), array( 'oauth_verifier' => Request::i()->oauth_verifier ), Request::i()->oauth_token )->decodeQueryString('oauth_token');
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'twitter' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'twitter' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Get user id */
|
||||
@@ -182,25 +206,25 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'oauth' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'oauth' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Has this user signed in with this service before? */
|
||||
try
|
||||
{
|
||||
$oauthAccess = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $userId ) )->first();
|
||||
$member = \IPS\Member::load( $oauthAccess['token_member'] );
|
||||
$oauthAccess = Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $userId ) )->first();
|
||||
$member = Member::load( $oauthAccess['token_member'] );
|
||||
|
||||
/* If the user never finished the linking process, or the account has been deleted, discard this access token */
|
||||
if ( !$oauthAccess['token_linked'] or !$member->member_id )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
throw new \UnderflowException;
|
||||
Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
throw new UnderflowException;
|
||||
}
|
||||
|
||||
/* Otherwise, update our token... */
|
||||
\IPS\Db::i()->update( 'core_login_links', array(
|
||||
Db::i()->update( 'core_login_links', array(
|
||||
'token_access_token' => $response['oauth_token'],
|
||||
'token_secret' => $response['oauth_token_secret'],
|
||||
), array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
@@ -209,7 +233,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
return $member;
|
||||
}
|
||||
/* No, create or link the account */
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
/* Get the username + email */
|
||||
$name = NULL;
|
||||
@@ -227,9 +251,9 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
|
||||
try
|
||||
{
|
||||
if ( $login->type === \IPS\Login::LOGIN_UCP )
|
||||
if ( $login->type === Login::LOGIN_UCP )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception = new Exception( 'generic_error', Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception->handler = $this;
|
||||
$exception->member = $login->reauthenticateAs;
|
||||
throw $exception;
|
||||
@@ -237,7 +261,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
|
||||
$member = $this->createAccount( $name, $email );
|
||||
|
||||
\IPS\Db::i()->insert( 'core_login_links', array(
|
||||
Db::i()->insert( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $member->member_id,
|
||||
'token_identifier' => $userId,
|
||||
@@ -268,23 +292,23 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Login\Exception $exception )
|
||||
catch ( Exception $exception )
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
if ( $exception->getCode() === Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
{
|
||||
try
|
||||
{
|
||||
$identifier = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
$identifier = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
|
||||
if( $identifier != $userId )
|
||||
{
|
||||
$exception->setCode( \IPS\Login\Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
$exception->setCode( Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_login_links', array(
|
||||
Db::i()->insert( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $exception->member->member_id,
|
||||
'token_identifier' => $userId,
|
||||
@@ -303,53 +327,54 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
abstract protected function authorizationEndpoint( \IPS\Login $login );
|
||||
abstract protected function authorizationEndpoint( Login $login ): Url;
|
||||
|
||||
/**
|
||||
* Token Request Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
abstract protected function tokenRequestEndpoint();
|
||||
abstract protected function tokenRequestEndpoint(): Url;
|
||||
|
||||
/**
|
||||
* Access Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
abstract protected function accessTokenEndpoint();
|
||||
abstract protected function accessTokenEndpoint(): Url;
|
||||
|
||||
/**
|
||||
* Redirection Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
public function redirectionEndpoint()
|
||||
public function redirectionEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'oauth/callback/', 'none' );
|
||||
return Url::internal( 'oauth/callback/', 'none' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Request
|
||||
*
|
||||
* @param string $method HTTP Method
|
||||
* @param \IPS\Http\Url $url URL
|
||||
* @param array|string|NULL $params Parameters
|
||||
* @param string $token OAuth Token
|
||||
* @param array $otherParams Other params to send obvs
|
||||
* @param string $mimeBoundary Mime data to send (boundary => data )
|
||||
* @return \IPS\Http\Response
|
||||
* @param string $method HTTP Method
|
||||
* @param Url $url URL
|
||||
* @param array|string|null $params Parameters
|
||||
* @param string $token OAuth Token
|
||||
* @param string $secret OAuth Secret
|
||||
* @param array $otherParams Other params to send obvs
|
||||
* @param array|string $mimeBoundary Mime data to send (boundary => data )
|
||||
* @return Response
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
*/
|
||||
protected function _sendRequest( $method, $url, $params=array(), $token='', $secret='', $otherParams=array(), $mimeBoundary=array() )
|
||||
protected function _sendRequest( string $method, Url $url, array|string|null $params=array(), string $token='', string $secret='', array $otherParams=array(), array|string $mimeBoundary=array() ): Response
|
||||
{
|
||||
/* Generate the OAUTH Authorization Header */
|
||||
$OAuthAuthorization = array_merge( array(
|
||||
'oauth_consumer_key' => $this->settings['consumer_key'],
|
||||
'oauth_nonce' => md5( \IPS\Login::generateRandomString() ),
|
||||
'oauth_nonce' => md5( Login::generateRandomString() ),
|
||||
'oauth_signature_method'=> 'HMAC-SHA1',
|
||||
'oauth_timestamp' => time(),
|
||||
'oauth_token' => $token,
|
||||
@@ -392,7 +417,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
$headers = array( 'Authorization' => $OAuthAuthorizationHeader );
|
||||
|
||||
/* Send the request */
|
||||
if ( ! \count( $mimeBoundary ) )
|
||||
if ( ! count( $mimeBoundary ) )
|
||||
{
|
||||
if ( $method === 'get' )
|
||||
{
|
||||
@@ -415,17 +440,17 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
* Get user's identifier (may not be a number)
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userId( \IPS\Member $member )
|
||||
public function userId( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $error['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedUserId( $link['token_access_token'], $link['token_secret'] );
|
||||
@@ -435,17 +460,17 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedUserName( $link['token_access_token'], $link['token_secret'] );
|
||||
@@ -455,17 +480,17 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
* Get user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userEmail( \IPS\Member $member )
|
||||
public function userEmail( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $error['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedEmail( $link['token_access_token'], $link['token_secret'] );
|
||||
@@ -476,10 +501,10 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Show in Account Settings?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member, or NULL for if it should show generally
|
||||
* @param Member|NULL $member The member, or NULL for if it should show generally
|
||||
* @return bool
|
||||
*/
|
||||
public function showInUcp( \IPS\Member $member = NULL )
|
||||
public function showInUcp( Member $member = NULL ): bool
|
||||
{
|
||||
$return = parent::showInUcp( $member );
|
||||
|
||||
@@ -495,7 +520,7 @@ abstract class _OAuth1 extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSyncOptions()
|
||||
public function hasSyncOptions(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -11,23 +11,39 @@
|
||||
namespace IPS\Login\Handler\OAuth1;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Data\Store;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Http\Request\Exception;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception as LoginException;
|
||||
use IPS\Login\Handler\OAuth1;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Login Handler
|
||||
*/
|
||||
class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
class Twitter extends OAuth1
|
||||
{
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Twitter';
|
||||
}
|
||||
@@ -35,19 +51,18 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
/**
|
||||
* 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_methods.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['login_acp_desc'] = \IPS\Member::loggedIn()->language()->addToStack('login_acp_will_reauth');
|
||||
Member::loggedIn()->language()->words['login_acp_desc'] = Member::loggedIn()->language()->addToStack('login_acp_will_reauth');
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
'name' => new \IPS\Helpers\Form\Radio( 'login_real_name', ( isset( $this->settings['name'] ) ) ? $this->settings['name'] : 'any', TRUE, array(
|
||||
'name' => new Radio( 'login_real_name', ( isset( $this->settings['name'] ) ) ? $this->settings['name'] : 'any', TRUE, array(
|
||||
'options' => array(
|
||||
'real' => 'login_twitter_name_real',
|
||||
'screen' => 'login_twitter_name_screen',
|
||||
@@ -60,9 +75,7 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
), NULL, NULL, NULL, 'login_real_name' ),
|
||||
),
|
||||
parent::acpForm(),
|
||||
array(
|
||||
'allow_status_import' => new \IPS\Helpers\Form\YesNo( 'login_generic_allow_status_import', ( isset( $this->settings['allow_status_import'] ) ) ? $this->settings['allow_status_import'] : FALSE, FALSE )
|
||||
)
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,9 +84,9 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return '#000000';
|
||||
return '#1d9bf0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,9 +94,9 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return 'twitter';
|
||||
return 'x-twitter';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +104,7 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return 'login_twitter';
|
||||
}
|
||||
@@ -101,42 +114,42 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_twitter';
|
||||
return 'ipsSocial--twitter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/X.png', 'core', 'interface' );
|
||||
return Theme::i()->resource( 'logos/login/Twitter.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
$return = \IPS\Http\Url::external('https://api.twitter.com/oauth/authenticate');
|
||||
$return = Url::external('https://api.twitter.com/oauth/authenticate');
|
||||
|
||||
if ( $login->type === \IPS\Login::LOGIN_ACP or $login->type === \IPS\Login::LOGIN_REAUTHENTICATE )
|
||||
if ( $login->type === Login::LOGIN_ACP or $login->type === Login::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
$return = $return->setQueryString( 'force_login', 'true' );
|
||||
}
|
||||
|
||||
if ( $login->type === \IPS\Login::LOGIN_REAUTHENTICATE )
|
||||
if ( $login->type === Login::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
try
|
||||
{
|
||||
$token = \IPS\Db::i()->select( array( 'token_access_token', 'token_secret' ), 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $login->reauthenticateAs->member_id ) )->first();
|
||||
$token = Db::i()->select( array( 'token_access_token', 'token_secret' ), 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $login->reauthenticateAs->member_id ) )->first();
|
||||
$userDetails = $this->_userData( $token['token_access_token'], $token['token_secret'] );
|
||||
if ( isset( $userDetails['screen_name'] ) )
|
||||
{
|
||||
@@ -152,21 +165,21 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
/**
|
||||
* Token Request Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenRequestEndpoint()
|
||||
protected function tokenRequestEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://api.twitter.com/oauth/request_token');
|
||||
return Url::external('https://api.twitter.com/oauth/request_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Access Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function accessTokenEndpoint()
|
||||
protected function accessTokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://api.twitter.com/oauth/access_token');
|
||||
return Url::external('https://api.twitter.com/oauth/access_token');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +189,7 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return string
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken, $accessTokenSecret )
|
||||
protected function authenticatedUserId( string $accessToken, string $accessTokenSecret ): string
|
||||
{
|
||||
return $this->_userData( $accessToken, $accessTokenSecret )['id'];
|
||||
}
|
||||
@@ -185,11 +198,11 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
* Get authenticated user's username
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken, $accessTokenSecret )
|
||||
protected function authenticatedUserName( string $accessToken, string $accessTokenSecret ): ?string
|
||||
{
|
||||
if ( $this->settings['name'] == 'screen' )
|
||||
{
|
||||
@@ -206,11 +219,11 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
* Get authenticated user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken, $accessTokenSecret )
|
||||
protected function authenticatedEmail( string $accessToken, string $accessTokenSecret ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken, $accessTokenSecret )['email'];
|
||||
}
|
||||
@@ -219,17 +232,17 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'], $link['token_secret'] )['screen_name'];
|
||||
@@ -239,23 +252,23 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'], $link['token_secret'] );
|
||||
if ( !$userData['default_profile_image'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( str_replace( '_normal', '', $userData['profile_image_url_https'] ?: $userData['profile_image_url'] ) );
|
||||
return Url::external( str_replace( '_normal', '', $userData['profile_image_url_https'] ?: $userData['profile_image_url'] ) );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -264,62 +277,27 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
* Get user's cover photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userCoverPhoto( \IPS\Member $member )
|
||||
public function userCoverPhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'], $link['token_secret'] );
|
||||
if ( isset( $userData['profile_banner_url'] ) and $userData['profile_banner_url'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['profile_banner_url'] );
|
||||
return Url::external( $userData['profile_banner_url'] );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's statuses since a particular date
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param \IPS\DateTime|NULL $since Date/Time to get statuses since then, or NULL to get the latest one
|
||||
* @return array
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userStatuses( \IPS\Member $member, \IPS\DateTime $since = NULL )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$return = array();
|
||||
$response = $this->_sendRequest( 'get', \IPS\Http\Url::external('https://api.twitter.com/1.1/statuses/user_timeline.json'), $since ? array() : array( 'count' => 1 ), $link['token_access_token'], $link['token_secret'] )->decodeJson();
|
||||
foreach ( $response as $statusData )
|
||||
{
|
||||
if ( $since and strtotime( $statusData['created_at'] ) < $since->getTimestamp() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$status = \IPS\core\Statuses\Status::createItem( $member, $member->ip_address, new \IPS\DateTime( $statusData['created_at'] ) );
|
||||
$status->content = $this->_parseStatusText( $member, nl2br( $statusData['text'], FALSE ) );
|
||||
|
||||
$return[] = $status;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get link to user's remote profile
|
||||
@@ -327,24 +305,24 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
*
|
||||
* @param string $identifier The ID Nnumber/string from remote service
|
||||
* @param string $username The username from remote service
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userLink( $identifier, $username )
|
||||
public function userLink( string $identifier, string $username ): ?Url
|
||||
{
|
||||
return \IPS\Http\Url::external( "https://twitter.com/" )->setPath( $username );
|
||||
return Url::external( "https://twitter.com/" )->setPath( $username );
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -361,36 +339,31 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
$return[] = 'photo';
|
||||
$return[] = 'cover';
|
||||
|
||||
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] )
|
||||
{
|
||||
$return[] = 'status';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return array
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
protected function _userData( $accessToken, $accessTokenSecret )
|
||||
protected function _userData( string $accessToken, string $accessTokenSecret ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = $this->_sendRequest( 'get', \IPS\Http\Url::external('https://api.twitter.com/1.1/account/verify_credentials.json'), array( 'include_email' => 'true' ), $accessToken, $accessTokenSecret )->decodeJson();
|
||||
$response = $this->_sendRequest( 'get', Url::external('https://api.twitter.com/1.1/account/verify_credentials.json'), array( 'include_email' => 'true' ), $accessToken, $accessTokenSecret )->decodeJson();
|
||||
if ( isset( $response['errors'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['errors'][0]['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( $response['errors'][0]['message'], LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$this->_cachedUserData[ $accessToken ] = $response;
|
||||
@@ -402,21 +375,21 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
/**
|
||||
* @brief The length of the shortened URLs returned by Twitter's text parser https://developer.twitter.com/en/docs/counting-characters#:~:text=The%20current%20length%20of%20a,count%20towards%20the%20character%20limit.
|
||||
*/
|
||||
protected static $defaultShortenedUrlLength = 23;
|
||||
protected static int $defaultShortenedUrlLength = 23;
|
||||
|
||||
/**
|
||||
* Post something to Twitter
|
||||
*
|
||||
* @param \IPS\Member $member Member posting
|
||||
* @param Member $member Member posting
|
||||
* @param string $content Content to post
|
||||
* @param \IPS\Http\Url|NULL $url Optional link
|
||||
* @return void
|
||||
* @param Url|NULL $url Optional link
|
||||
* @return bool
|
||||
*/
|
||||
public function postToTwitter( \IPS\Member $member, $content, \IPS\Http\Url $url = NULL )
|
||||
public function postToTwitter( Member $member, string $content, ?Url $url = NULL ) : bool
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array( 'status' => $content );
|
||||
@@ -425,32 +398,32 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
if ( $url !== NULL )
|
||||
{
|
||||
/* Try to refresh if the stored response is more than a day old */
|
||||
if ( !isset( \IPS\Data\Store::i()->twitter_config ) or \IPS\Data\Store::i()->twitter_config['time'] > time() - 86400 )
|
||||
if ( !isset( Store::i()->twitter_config ) or Store::i()->twitter_config['time'] > time() - 86400 )
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = $this->_sendRequest( 'get', \IPS\Http\Url::external('https://api.twitter.com/1.1/help/configuration.json'), array(), $link['token_access_token'], $link['token_secret'] );
|
||||
if ( ( $response->httpResponseCode === 200 ) AND ( $payload = $response->decodeJson() ) AND \is_array( $payload ) )
|
||||
$response = $this->_sendRequest( 'get', Url::external('https://api.twitter.com/1.1/help/configuration.json'), array(), $link['token_access_token'], $link['token_secret'] );
|
||||
if ( ( $response->httpResponseCode === 200 ) AND ( $payload = $response->decodeJson() ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->twitter_config = array_merge( $payload, array( 'time' => time() ) );
|
||||
Store::i()->twitter_config = array_merge( $payload, array( 'time' => time() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \UnexpectedValueException();
|
||||
throw new UnexpectedValueException();
|
||||
}
|
||||
}
|
||||
catch ( \Exception|\UnexpectedValueException $e )
|
||||
catch ( \Exception| UnexpectedValueException $e )
|
||||
{
|
||||
/* We should be fine hard coding to 23 if the deprecated configuration endpoint is no longer online https://developer.twitter.com/en/docs/counting-characters#:~:text=The%20current%20length%20of%20a,count%20towards%20the%20character%20limit. */
|
||||
\IPS\Data\Store::i()->twitter_config['short_url_length'] = static::$defaultShortenedUrlLength;
|
||||
Store::i()->twitter_config['short_url_length'] = static::$defaultShortenedUrlLength;
|
||||
}
|
||||
}
|
||||
|
||||
$maxUrlLen = \IPS\Data\Store::i()->twitter_config['short_url_length'] ?? static::$defaultShortenedUrlLength;
|
||||
$maxUrlLen = Store::i()->twitter_config['short_url_length'] ?? static::$defaultShortenedUrlLength;
|
||||
$data['status'] = mb_substr( $data['status'], 0, ( 140 - ( $maxUrlLen + 1 ) ) ) . ' ' . $url;
|
||||
}
|
||||
|
||||
$response = $this->_sendRequest( 'post', \IPS\Http\Url::external('https://api.twitter.com/1.1/statuses/update.json'), $data, $link['token_access_token'], $link['token_secret'] )->decodeJson();
|
||||
$response = $this->_sendRequest( 'post', Url::external('https://api.twitter.com/1.1/statuses/update.json'), $data, $link['token_access_token'], $link['token_secret'] )->decodeJson();
|
||||
|
||||
return isset( $response['id_str'] );
|
||||
}
|
||||
@@ -460,10 +433,10 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
/**
|
||||
* Request a token
|
||||
*
|
||||
* @param \IPS\Http\Url $callback Callback URK
|
||||
* @return string
|
||||
* @param Url $callback Callback URL
|
||||
* @return array
|
||||
*/
|
||||
public function requestToken( $callback )
|
||||
public function requestToken( Url $callback ): array
|
||||
{
|
||||
return $this->_sendRequest( 'get', $this->tokenRequestEndpoint(), array( 'oauth_callback' => (string) $callback ) )->decodeQueryString('oauth_token');
|
||||
}
|
||||
@@ -471,11 +444,11 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $verifier Verifier
|
||||
* @param string $accessTokenSecret Access Token
|
||||
* @param string $verifier Verifier
|
||||
* @param string $accessToken Access Token
|
||||
* @return array
|
||||
*/
|
||||
public function exchangeToken( $verifier, $accessToken )
|
||||
public function exchangeToken(string $verifier, string $accessToken ): array
|
||||
{
|
||||
return $this->_sendRequest( 'post', $this->accessTokenEndpoint(), array( 'oauth_verifier' => $verifier ), $accessToken )->decodeQueryString('user_id');
|
||||
}
|
||||
@@ -483,15 +456,15 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
/**
|
||||
* Can we publish to this twitter account?
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasWritePermissions( $accessToken, $accessTokenSecret )
|
||||
public function hasWritePermissions( string $accessToken, string $accessTokenSecret ): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = $this->_sendRequest( 'get', \IPS\Http\Url::external('https://api.twitter.com/1.1/account/verify_credentials.json'), array(), $accessToken, $accessTokenSecret );
|
||||
$response = $this->_sendRequest( 'get', Url::external('https://api.twitter.com/1.1/account/verify_credentials.json'), array(), $accessToken, $accessTokenSecret );
|
||||
|
||||
if ( $response->httpResponseCode == 401 )
|
||||
{
|
||||
@@ -507,33 +480,20 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e ) { }
|
||||
catch ( Exception $e ) { }
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a status
|
||||
*
|
||||
* @param array $data Post data to send
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return boolean
|
||||
*/
|
||||
public function sendStatus( $data, $accessToken, $accessTokenSecret )
|
||||
{
|
||||
return $this->_sendRequest( 'post', \IPS\Http\Url::external("https://api.twitter.com/1.1/statuses/update.json"), $data, $accessToken, $accessTokenSecret )->decodeJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send media
|
||||
*
|
||||
* @param array $contents Photo contents
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return boolean
|
||||
* @param array $contents Photo contents
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessTokenSecret Access Token Secret
|
||||
* @return array
|
||||
*/
|
||||
public function sendMedia( $contents, $accessToken, $accessTokenSecret )
|
||||
public function sendMedia( array $contents, string $accessToken, string $accessTokenSecret ): array
|
||||
{
|
||||
$mimeBoundary = sha1( microtime() );
|
||||
|
||||
@@ -543,48 +503,6 @@ class _Twitter extends \IPS\Login\Handler\OAuth1
|
||||
$data .= $contents . "\r\n";
|
||||
$data .= '--' . $mimeBoundary . '--' . "\r\n" . "\r\n";
|
||||
|
||||
return $this->_sendRequest( 'post', \IPS\Http\Url::external('https://upload.twitter.com/1.1/media/upload.json'), array(), $accessToken, $accessTokenSecret, array(), array( $mimeBoundary, $data ) )->decodeJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Set whether this node is enabled
|
||||
*
|
||||
* @param bool|int $enabled Whether to set it enabled or disabled
|
||||
* @return void
|
||||
*/
|
||||
protected function set__enabled( $enabled )
|
||||
{
|
||||
parent::set__enabled( $enabled );
|
||||
$this->_checkSocialPromotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
$this->_checkSocialPromotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable social promotes Twitter method if all Twitter login handlers are disabled
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _checkSocialPromotes()
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->select( '*', 'core_login_methods', array( 'login_classname=? AND login_id!=?', 'IPS\Login\Handler\OAuth1\Twitter', $this->_id ) )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
$promoter = \IPS\core\Promote::getPromoter('Twitter');
|
||||
$promoter->enabled = FALSE;
|
||||
$promoter->save();
|
||||
}
|
||||
return $this->_sendRequest( 'post', Url::external('https://upload.twitter.com/1.1/media/upload.json'), array(), $accessToken, $accessTokenSecret, array(), array( $mimeBoundary, $data ) )->decodeJson();
|
||||
}
|
||||
}
|
||||
@@ -11,23 +11,38 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use Exception;
|
||||
use IPS\File;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Upload;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception as LoginException;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign In With Apple Login Handler
|
||||
*/
|
||||
class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
class Apple extends OpenID
|
||||
{
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Apple';
|
||||
}
|
||||
@@ -37,7 +52,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
protected function _authenticationType(): string
|
||||
{
|
||||
return static::AUTHENTICATE_POST;
|
||||
}
|
||||
@@ -50,9 +65,9 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_id'] = \IPS\Member::loggedIn()->language()->addToStack('login_apple_services_id');
|
||||
Member::loggedIn()->language()->words['oauth_client_id'] = Member::loggedIn()->language()->addToStack('login_apple_services_id');
|
||||
|
||||
$return = array();
|
||||
$return[] = array( 'login_handler_apple_settings', 'login_handler_Apple_info' );
|
||||
@@ -66,16 +81,16 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
{
|
||||
$active = 'accountManagementSettings';
|
||||
}
|
||||
if ( !\is_string( $v ) and !\is_array( $v ) and $k !== 'client_secret' )
|
||||
if ( !is_string( $v ) and !is_array( $v ) and $k !== 'client_secret' )
|
||||
{
|
||||
${$active}[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$return['apple_team_id'] = new \IPS\Helpers\Form\Text( 'apple_team_id', isset( $this->settings['apple_team_id'] ) ? $this->settings['apple_team_id'] : NULL, NULL, array(), NULL, NULL, NULL, 'apple_team_id' );
|
||||
$return['apple_key_id'] = new \IPS\Helpers\Form\Text( 'apple_key_id', isset( $this->settings['apple_key_id'] ) ? $this->settings['apple_key_id'] : NULL, NULL, array(), NULL, NULL, NULL, 'apple_key_id' );
|
||||
$return['apple_key'] = new \IPS\Helpers\Form\Upload( 'apple_key', ( isset( $this->settings['apple_key'] ) and $this->settings['apple_key'] ) ? \IPS\File::get( 'core_Login', $this->settings['apple_key'] ) : NULL, TRUE, array( 'storageExtension' => 'core_Login', 'allowedFileTypes' => ['p8'] ), NULL, NULL, NULL, 'apple_key' );;
|
||||
|
||||
$return['apple_team_id'] = new Text( 'apple_team_id', $this->settings['apple_team_id'] ?? NULL, NULL, array(), NULL, NULL, NULL, 'apple_team_id' );
|
||||
$return['apple_key_id'] = new Text( 'apple_key_id', $this->settings['apple_key_id'] ?? NULL, NULL, array(), NULL, NULL, NULL, 'apple_key_id' );
|
||||
$return['apple_key'] = new Upload( 'apple_key', ( isset( $this->settings['apple_key'] ) and $this->settings['apple_key'] ) ? File::get( 'core_Login', $this->settings['apple_key'] ) : NULL, TRUE, array( 'storageExtension' => 'core_Login', 'allowedFileTypes' => ['p8'] ), NULL, NULL, NULL, 'apple_key' );
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
foreach ( $accountManagementSettings as $k => $v )
|
||||
{
|
||||
@@ -91,7 +106,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
public function acpFormSave( array &$values ): array
|
||||
{
|
||||
$return = parent::acpFormSave( $values );
|
||||
$return['apple_key'] = (string) $return['apple_key'];
|
||||
@@ -104,7 +119,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return '#000000';
|
||||
}
|
||||
@@ -114,7 +129,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return 'apple';
|
||||
}
|
||||
@@ -124,7 +139,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return 'login_apple';
|
||||
}
|
||||
@@ -134,20 +149,20 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_apple';
|
||||
return 'ipsSocial--apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/Apple.png', 'core', 'interface' );
|
||||
return Theme::i()->resource( 'logos/login/Apple.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +170,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
@@ -163,46 +178,42 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
/**
|
||||
* Get scopes to request
|
||||
*
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @param array|null $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
$return = array( 'name email' );
|
||||
|
||||
return $return;
|
||||
return array( 'name email' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
$return = \IPS\Http\Url::external( 'https://appleid.apple.com/auth/authorize' )->setQueryString( 'response_mode', 'form_post' );
|
||||
|
||||
return $return;
|
||||
return Url::external( 'https://appleid.apple.com/auth/authorize' )->setQueryString( 'response_mode', 'form_post' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external( 'https://appleid.apple.com/auth/token' );
|
||||
return Url::external( 'https://appleid.apple.com/auth/token' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
$claims = $this->getClaimsfromIdToken( $this->_getIdToken( $accessToken ) );
|
||||
|
||||
@@ -213,10 +224,10 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* Get authenticated user's username
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
$name = NULL;
|
||||
|
||||
@@ -237,10 +248,10 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* Get authenticated user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
$claims = $this->getClaimsfromIdToken( $this->_getIdToken( $accessToken ) );
|
||||
|
||||
@@ -251,9 +262,9 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* Client Secret
|
||||
*
|
||||
* @return string | NULL
|
||||
* @throws \IPS\Login\Exception
|
||||
* @throws LoginException
|
||||
*/
|
||||
public function clientSecret()
|
||||
public function clientSecret(): ?string
|
||||
{
|
||||
$key = NULL;
|
||||
|
||||
@@ -261,9 +272,9 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
{
|
||||
try
|
||||
{
|
||||
$key = \IPS\File::get( 'core_Login', $this->settings['apple_key'] )->contents();
|
||||
$key = File::get( 'core_Login', $this->settings['apple_key'] )->contents();
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -295,7 +306,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
|
||||
if ( !$pKey )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'login_apple_invalid_key', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( 'login_apple_invalid_key', LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$payload = $this->baseURL64encode( json_encode( $header ) ) . '.' . $this->baseURL64encode( json_encode( $data ) );
|
||||
@@ -304,7 +315,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
$success = openssl_sign( $payload, $signature, $pKey, OPENSSL_ALGO_SHA256 );
|
||||
if ( !$success )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( 'generic_error', LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$rawSignature = $this->fromDER( $signature, 64 );
|
||||
@@ -315,16 +326,16 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
$scopes = $this->authorizedScopes( $member );
|
||||
|
||||
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( $scopes and \in_array( 'email', $scopes ) ) )
|
||||
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( $scopes and in_array( 'email', $scopes ) ) )
|
||||
{
|
||||
$return[] = 'email';
|
||||
}
|
||||
@@ -335,10 +346,10 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
/**
|
||||
* Process an ID Token
|
||||
*
|
||||
* @param array $iDToken ID Token
|
||||
* @param string $iDToken ID Token
|
||||
* @return array
|
||||
*/
|
||||
protected function getClaimsfromIdToken ( $iDToken )
|
||||
protected function getClaimsfromIdToken ( string $iDToken ): array
|
||||
{
|
||||
$claims = explode( '.', $iDToken )[1];
|
||||
$claims = json_decode( base64_decode( $claims ), true );
|
||||
@@ -353,15 +364,15 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* @param int $partLength
|
||||
*
|
||||
* @return string
|
||||
* @throws \IPS\Login\Exception
|
||||
* @throws LoginException
|
||||
*/
|
||||
public static function fromDER( string $der, int $partLength )
|
||||
public static function fromDER( string $der, int $partLength ): string
|
||||
{
|
||||
$hex = unpack( 'H*', $der )[1];
|
||||
|
||||
if ( '30' !== mb_substr( $hex, 0, 2, '8bit' ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( 'generic_error', LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( '81' === mb_substr( $hex, 2, 2, '8bit' ) )
|
||||
@@ -375,7 +386,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
|
||||
if ( '02' !== mb_substr( $hex, 0, 2, '8bit' ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( 'generic_error', LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$Rl = hexdec( mb_substr( $hex, 2, 2, '8bit' ) );
|
||||
@@ -385,7 +396,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
|
||||
if ( '02' !== mb_substr( $hex, 0, 2, '8bit' ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( 'generic_error', LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$Sl = hexdec( mb_substr( $hex, 2, 2, '8bit' ) );
|
||||
@@ -401,7 +412,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected static function preparePositiveInteger( string $data )
|
||||
protected static function preparePositiveInteger( string $data ): string
|
||||
{
|
||||
if ( mb_substr( $data, 0, 2, '8bit') > '7f' )
|
||||
{
|
||||
@@ -421,7 +432,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected static function retrievePositiveInteger( string $data )
|
||||
protected static function retrievePositiveInteger( string $data ): string
|
||||
{
|
||||
while ( '00' === mb_substr( $data, 0, 2, '8bit' ) && mb_substr( $data, 2, 2, '8bit' ) > '7f' )
|
||||
{
|
||||
@@ -437,7 +448,7 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected function baseURL64encode( $data )
|
||||
protected function baseURL64encode( string $data ): string
|
||||
{
|
||||
$encoded = strtr( base64_encode( $data ), '+/', '-_' );
|
||||
|
||||
@@ -447,17 +458,17 @@ class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
/**
|
||||
* Delete files
|
||||
*
|
||||
* @return void
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
public function delete(): void
|
||||
{
|
||||
if ( isset( $this->settings['apple_key'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Login', $this->settings['apple_key'] )->delete();
|
||||
File::get( 'core_Login', $this->settings['apple_key'] )->delete();
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
catch( Exception $e ){}
|
||||
}
|
||||
|
||||
parent::delete();
|
||||
|
||||
@@ -12,28 +12,53 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\File;
|
||||
use IPS\Helpers\Form\Color;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Stack;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Helpers\Form\Upload;
|
||||
use IPS\Helpers\Form\Url as FormUrl;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Log;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use const IPS\OAUTH_REQUIRES_HTTPS;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom OAuth 2 Login Handler
|
||||
*/
|
||||
class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
class Custom extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @brief Can we have multiple instances of this handler?
|
||||
*/
|
||||
public static $allowMultiple = TRUE;
|
||||
public static bool $allowMultiple = TRUE;
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_custom_oauth';
|
||||
}
|
||||
@@ -46,13 +71,13 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
$return[] = array( 'login_handler_oauth_settings', 'login_handler_custom_oauth_info' );
|
||||
|
||||
$return['grant_type'] = new \IPS\Helpers\Form\Radio( 'oauth_custom_grant_type', isset( $this->settings['grant_type'] ) ? $this->settings['grant_type'] : 'authorization_code', TRUE, array(
|
||||
$return['grant_type'] = new Radio( 'oauth_custom_grant_type', $this->settings['grant_type'] ?? 'authorization_code', TRUE, array(
|
||||
'options' => array(
|
||||
'authorization_code' => 'client_grant_type_authorization_code',
|
||||
'implicit' => 'client_grant_type_implicit',
|
||||
@@ -73,59 +98,59 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$active = 'accountManagementSettings';
|
||||
}
|
||||
if ( !\is_string( $v ) and !\is_array( $v ) )
|
||||
if ( !is_string( $v ) and !is_array( $v ) )
|
||||
{
|
||||
${$active}[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$return['authentication_type'] = new \IPS\Helpers\Form\Radio( 'oauth_custom_authentication_type', isset( $this->settings['authentication_type'] ) ? $this->settings['authentication_type'] : static::AUTHENTICATE_HEADER, TRUE, array(
|
||||
$return['authentication_type'] = new Radio( 'oauth_custom_authentication_type', $this->settings['authentication_type'] ?? static::AUTHENTICATE_HEADER, TRUE, array(
|
||||
'options' => array(
|
||||
static::AUTHENTICATE_HEADER => 'oauth_custom_authentication_type_header',
|
||||
static::AUTHENTICATE_POST => 'oauth_custom_authentication_type_post',
|
||||
)
|
||||
) );
|
||||
|
||||
$return['scopes'] = new \IPS\Helpers\Form\Stack( 'oauth_scopes_to_request', isset( $this->settings['scopes'] ) ? $this->settings['scopes'] : array(), FALSE, array() );
|
||||
$return['scopes'] = new Stack( 'oauth_scopes_to_request', $this->settings['scopes'] ?? array(), FALSE, array() );
|
||||
|
||||
$authorizationEndpointValidation = function( $val )
|
||||
{
|
||||
if ( \IPS\OAUTH_REQUIRES_HTTPS and $val and $val instanceof \IPS\Http\Url )
|
||||
if ( OAUTH_REQUIRES_HTTPS and $val and $val instanceof Url )
|
||||
{
|
||||
if ( $val->data[ \IPS\Http\Url::COMPONENT_SCHEME ] !== 'https' )
|
||||
if ( $val->data[ Url::COMPONENT_SCHEME ] !== 'https' )
|
||||
{
|
||||
throw new \DomainException('authorization_endpoint_https');
|
||||
throw new DomainException('authorization_endpoint_https');
|
||||
}
|
||||
if ( $val->data[ \IPS\Http\Url::COMPONENT_FRAGMENT ] )
|
||||
if ( $val->data[ Url::COMPONENT_FRAGMENT ] )
|
||||
{
|
||||
throw new \DomainException('authorization_endpoint_fragment');
|
||||
throw new DomainException('authorization_endpoint_fragment');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$return['authorization_endpoint'] = new \IPS\Helpers\Form\Url( 'oauth_authorization_endpoint', isset( $this->settings['authorization_endpoint'] ) ? $this->settings['authorization_endpoint'] : NULL, NULL, array( 'placeholder' => 'https://example.com/oauth/authorize' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint' );
|
||||
$return['authorization_endpoint_secure'] = new \IPS\Helpers\Form\Url( 'oauth_authorization_endpoint_secure', ( isset( $this->settings['authorization_endpoint_secure'] ) AND $this->settings['authorization_endpoint_secure'] ) ? $this->settings['authorization_endpoint_secure'] : NULL, NULL, array( 'nullLang' => 'oauth_authorization_endpoint_same', 'placeholder' => 'https://example.com/oauth/authorize/?prompt=login' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint_secure' );
|
||||
$return['token_endpoint'] = new \IPS\Helpers\Form\Url( 'oauth_token_endpoint', isset( $this->settings['token_endpoint'] ) ? $this->settings['token_endpoint'] : NULL, TRUE, array( 'placeholder' => 'https://www.example.com/oauth/token' ) );
|
||||
$return['user_endpoint'] = new \IPS\Helpers\Form\Url( 'oauth_user_endpoint', isset( $this->settings['user_endpoint'] ) ? $this->settings['user_endpoint'] : NULL, TRUE, array( 'placeholder' => 'https://www.example.com/oauth/me' ) );
|
||||
$return['uid_field'] = new \IPS\Helpers\Form\Text( 'oauth_custom_uid_field', isset( $this->settings['uid_field'] ) ? $this->settings['uid_field'] : NULL, TRUE, array() );
|
||||
$return['name_field'] = new \IPS\Helpers\Form\Text( 'oauth_custom_name_field', isset( $this->settings['name_field'] ) ? $this->settings['name_field'] : NULL, FALSE, array(), NULL, NULL, NULL, 'login_real_name' );
|
||||
$return['email_field'] = new \IPS\Helpers\Form\Text( 'oauth_custom_email_field', isset( $this->settings['email_field'] ) ? $this->settings['email_field'] : NULL, FALSE, array(), NULL, NULL, NULL, 'login_real_email' );
|
||||
$return['photo_field'] = new \IPS\Helpers\Form\Text( 'oauth_custom_photo_field', isset( $this->settings['photo_field'] ) ? $this->settings['photo_field'] : NULL );
|
||||
if ( \IPS\Settings::i()->allow_forgot_password == 'normal' or \IPS\Settings::i()->allow_forgot_password == 'handler' )
|
||||
$return['authorization_endpoint'] = new FormUrl( 'oauth_authorization_endpoint', $this->settings['authorization_endpoint'] ?? NULL, NULL, array( 'placeholder' => 'https://example.com/oauth/authorize' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint' );
|
||||
$return['authorization_endpoint_secure'] = new FormUrl( 'oauth_authorization_endpoint_secure', ( isset( $this->settings['authorization_endpoint_secure'] ) AND $this->settings['authorization_endpoint_secure'] ) ? $this->settings['authorization_endpoint_secure'] : NULL, NULL, array( 'nullLang' => 'oauth_authorization_endpoint_same', 'placeholder' => 'https://example.com/oauth/authorize/?prompt=login' ), $authorizationEndpointValidation, NULL, NULL, 'authorization_endpoint_secure' );
|
||||
$return['token_endpoint'] = new FormUrl( 'oauth_token_endpoint', $this->settings['token_endpoint'] ?? NULL, TRUE, array( 'placeholder' => 'https://www.example.com/oauth/token' ) );
|
||||
$return['user_endpoint'] = new FormUrl( 'oauth_user_endpoint', $this->settings['user_endpoint'] ?? NULL, TRUE, array( 'placeholder' => 'https://www.example.com/oauth/me' ) );
|
||||
$return['uid_field'] = new Text( 'oauth_custom_uid_field', $this->settings['uid_field'] ?? NULL, TRUE, array() );
|
||||
$return['name_field'] = new Text( 'oauth_custom_name_field', $this->settings['name_field'] ?? NULL, FALSE, array(), NULL, NULL, NULL, 'login_real_name' );
|
||||
$return['email_field'] = new Text( 'oauth_custom_email_field', $this->settings['email_field'] ?? NULL, FALSE, array(), NULL, NULL, NULL, 'login_real_email' );
|
||||
$return['photo_field'] = new Text( 'oauth_custom_photo_field', $this->settings['photo_field'] ?? NULL );
|
||||
if ( Settings::i()->allow_forgot_password == 'normal' or Settings::i()->allow_forgot_password == 'handler' )
|
||||
{
|
||||
$return['forgot_password_url'] = new \IPS\Helpers\Form\Url( 'handler_forgot_password_url', isset( $this->settings['forgot_password_url'] ) ? $this->settings['forgot_password_url'] : NULL, FALSE, array(), NULL, NULL, NULL, 'forgot_password_url' );
|
||||
\IPS\Member::loggedIn()->language()->words['handler_forgot_password_url_desc'] = \IPS\Member::loggedIn()->language()->addToStack( \IPS\Settings::i()->allow_forgot_password == 'normal' ? 'handler_forgot_password_url_desc_normal' : 'handler_forgot_password_url_deschandler' );
|
||||
$return['forgot_password_url'] = new FormUrl( 'handler_forgot_password_url', $this->settings['forgot_password_url'] ?? NULL, FALSE, array(), NULL, NULL, NULL, 'forgot_password_url' );
|
||||
Member::loggedIn()->language()->words['handler_forgot_password_url_desc'] = Member::loggedIn()->language()->addToStack( Settings::i()->allow_forgot_password == 'normal' ? 'handler_forgot_password_url_desc_normal' : 'handler_forgot_password_url_deschandler' );
|
||||
}
|
||||
|
||||
$return[] = 'login_handler_oauth_ui';
|
||||
$return['auth_types'] = new \IPS\Helpers\Form\Select( 'oauth_custom_auth_types', isset( $this->settings['auth_types'] ) ? $this->settings['auth_types'] : ( \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
$return['auth_types'] = new Select( 'oauth_custom_auth_types', $this->settings['auth_types'] ?? ( Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
Login::AUTH_TYPE_USERNAME => 'username',
|
||||
) ), NULL, NULL, NULL, 'oauth_custom_auth_types' );
|
||||
$return['button_color'] = new \IPS\Helpers\Form\Color( 'oauth_custom_button_color', isset( $this->settings['button_color'] ) ? $this->settings['button_color'] : '#478F79', NULL, array(), NULL, NULL, NULL, 'button_color' );
|
||||
$return['button_text'] = new \IPS\Helpers\Form\Translatable( 'oauth_custom_button_text', NULL, NULL, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('oauth_custom_button_text_custom_placeholder'), 'app' => 'core', 'key' => ( $this->id ? "core_custom_oauth_{$this->id}" : NULL ) ), NULL, NULL, NULL, 'button_text' );
|
||||
$return['button_icon'] = new \IPS\Helpers\Form\Upload( 'oauth_custom_button_icon', ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] ) : NULL, FALSE, array( 'storageExtension' => 'core_Login' ), NULL, NULL, NULL, 'button_icon' );
|
||||
$return['button_color'] = new Color( 'oauth_custom_button_color', $this->settings['button_color'] ?? '#478F79', NULL, array(), NULL, NULL, NULL, 'button_color' );
|
||||
$return['button_text'] = new Translatable( 'oauth_custom_button_text', NULL, NULL, array( 'placeholder' => Member::loggedIn()->language()->addToStack('oauth_custom_button_text_custom_placeholder'), 'app' => 'core', 'key' => ( $this->id ? "core_custom_oauth_{$this->id}" : NULL ) ), NULL, NULL, NULL, 'button_text' );
|
||||
$return['button_icon'] = new Upload( 'oauth_custom_button_icon', ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] ) : NULL, FALSE, array( 'storageExtension' => 'core_Login' ), NULL, NULL, NULL, 'button_icon' );
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
foreach ( $accountManagementSettings as $k => $v )
|
||||
@@ -142,7 +167,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
public function acpFormSave( array &$values ): array
|
||||
{
|
||||
$return = parent::acpFormSave( $values );
|
||||
$return['button_icon'] = (string) $return['button_icon'];
|
||||
@@ -158,7 +183,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
$parent = parent::formatFormValues( $values );
|
||||
|
||||
@@ -168,7 +193,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
\IPS\Lang::saveCustom( 'core', "core_custom_oauth_{$this->id}", $values['oauth_custom_button_text'] );
|
||||
Lang::saveCustom( 'core', "core_custom_oauth_{$this->id}", $values['oauth_custom_button_text'] );
|
||||
unset( $values['button_text'] );
|
||||
}
|
||||
|
||||
@@ -180,7 +205,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return $this->settings['button_color'];
|
||||
}
|
||||
@@ -190,28 +215,28 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] ) : NULL;
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] ) : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] )->url : NULL;
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] )->url : NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get logo to display in user cp sidebar
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForUcp()
|
||||
public function logoForUcp(): Url|string|null
|
||||
{
|
||||
return $this->logoForDeviceInformation();
|
||||
}
|
||||
@@ -221,7 +246,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return "core_custom_oauth_{$this->id}";
|
||||
}
|
||||
@@ -231,9 +256,9 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return isset( $this->settings['grant_type'] ) ? $this->settings['grant_type'] : 'authorization_code';
|
||||
return $this->settings['grant_type'] ?? 'authorization_code';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,18 +266,18 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
protected function _authenticationType(): string
|
||||
{
|
||||
return isset( $this->settings['authentication_type'] ) ? $this->settings['authentication_type'] : static::AUTHENTICATE_HEADER;
|
||||
return $this->settings['authentication_type'] ?? static::AUTHENTICATE_HEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scopes to request
|
||||
*
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @param array|null $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
return $this->settings['scopes'];
|
||||
}
|
||||
@@ -260,36 +285,36 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
if ( isset( $this->settings['authorization_endpoint_secure'] ) and $this->settings['authorization_endpoint_secure'] and ( $login->type === \IPS\Login::LOGIN_ACP or $login->type === \IPS\Login::LOGIN_REAUTHENTICATE ) )
|
||||
if ( isset( $this->settings['authorization_endpoint_secure'] ) and $this->settings['authorization_endpoint_secure'] and ( $login->type === Login::LOGIN_ACP or $login->type === Login::LOGIN_REAUTHENTICATE ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $this->settings['authorization_endpoint_secure'] );
|
||||
return Url::external( $this->settings['authorization_endpoint_secure'] );
|
||||
}
|
||||
|
||||
return \IPS\Http\Url::external( $this->settings['authorization_endpoint'] );
|
||||
return Url::external( $this->settings['authorization_endpoint'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external( $this->settings['token_endpoint'] );
|
||||
return Url::external( $this->settings['token_endpoint'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
if ( $userId = static::getValueFromArray( $this->_userData( $accessToken, $this->settings['uid_field'] ), $this->settings['uid_field'] ) )
|
||||
{
|
||||
@@ -305,7 +330,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['name_field'] ) and $this->settings['name_field'] and $username = static::getValueFromArray( $this->_userData( $accessToken, $this->settings['name_field'] ), $this->settings['name_field'] ) )
|
||||
{
|
||||
@@ -321,7 +346,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['email_field'] ) and $this->settings['email_field'] and $email = static::getValueFromArray( $this->_userData( $accessToken, $this->settings['email_field'] ), $this->settings['email_field'] ) )
|
||||
{
|
||||
@@ -334,24 +359,24 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( isset( $this->settings['photo_field'] ) and $this->settings['photo_field'] )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( $photo = static::getValueFromArray( $this->_userData( $link['token_access_token'], $this->settings['photo_field'] ), $this->settings['photo_field'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $photo );
|
||||
return Url::external( $photo );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,17 +387,17 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedUserName( $link['token_access_token'] );
|
||||
@@ -381,11 +406,11 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -410,21 +435,21 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param string $accessToken Access Token
|
||||
* @param string|null $expectedParam
|
||||
* @return array
|
||||
*/
|
||||
protected function _userData( $accessToken, $expectedParam = NULL )
|
||||
protected function _userData( string $accessToken, ?string $expectedParam = NULL ) : array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
/* Try the most sensible way first */
|
||||
$response = \IPS\Http\Url::external( $this->settings['user_endpoint'] )->request()
|
||||
$response = Url::external( $this->settings['user_endpoint'] )->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
) )
|
||||
@@ -437,7 +462,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( static::getValueFromArray( $response, $expectedParam ) === NULL )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['user_endpoint'] )->setQueryString( 'access_token', $accessToken )->request()
|
||||
$response = Url::external( $this->settings['user_endpoint'] )->setQueryString( 'access_token', $accessToken )->request()
|
||||
->get()
|
||||
->decodeJson();
|
||||
}
|
||||
@@ -446,8 +471,8 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
/* Check for any errors */
|
||||
if ( $response === NULL OR static::getValueFromArray( $response, $this->settings['uid_field'] ) === NULL )
|
||||
{
|
||||
\IPS\Log::log( print_r( $response, TRUE ), 'oauth_custom' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( print_r( $response, TRUE ), 'oauth_custom' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Set */
|
||||
@@ -459,11 +484,11 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Get value from an array
|
||||
*
|
||||
* @param array $array The array with the data
|
||||
* @param string $key The key using[square][brackets]
|
||||
* @param array $array The array with the data
|
||||
* @param string $key The key using[square][brackets]
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function getValueFromArray( $array, $key )
|
||||
protected static function getValueFromArray( array $array, string $key ): mixed
|
||||
{
|
||||
while ( $pos = mb_strpos( $key, '[' ) )
|
||||
{
|
||||
@@ -489,10 +514,10 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Forgot Password URL
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forgotPasswordUrl()
|
||||
public function forgotPasswordUrl(): ?Url
|
||||
{
|
||||
return ( isset( $this->settings['forgot_password_url'] ) and $this->settings['forgot_password_url'] ) ? \IPS\Http\Url::external( $this->settings['forgot_password_url'] ) : NULL;
|
||||
return ( isset( $this->settings['forgot_password_url'] ) and $this->settings['forgot_password_url'] ) ? Url::external( $this->settings['forgot_password_url'] ) : NULL;
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,17 @@
|
||||
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
use Exception as PHPException;
|
||||
|
||||
/**
|
||||
* OAuth2 Exception
|
||||
*/
|
||||
class _Exception extends \Exception
|
||||
class Exception extends PHPException
|
||||
{
|
||||
/**
|
||||
* @brief Description of the error
|
||||
*/
|
||||
public $description;
|
||||
public ?string $description = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -27,7 +29,7 @@ class _Exception extends \Exception
|
||||
* @param string|NULL $description Error Description or NULL.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $message, $description = null )
|
||||
public function __construct( string $message, ?string $description = null )
|
||||
{
|
||||
parent::__construct( $message );
|
||||
$this->description = $description;
|
||||
|
||||
@@ -11,23 +11,40 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
use DomainException;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Http\Request\Exception;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception as LoginException;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function is_numeric;
|
||||
use const IPS\CIC;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Facebook Login Handler
|
||||
*/
|
||||
class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
class Facebook extends OAuth2
|
||||
{
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Facebook';
|
||||
}
|
||||
@@ -40,15 +57,15 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['login_acp_desc'] = \IPS\Member::loggedIn()->language()->addToStack('login_acp_will_reauth');
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_id'] = \IPS\Member::loggedIn()->language()->addToStack('login_facebook_app');
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_client_secret'] = \IPS\Member::loggedIn()->language()->addToStack('login_facebook_secret');
|
||||
Member::loggedIn()->language()->words['login_acp_desc'] = Member::loggedIn()->language()->addToStack('login_acp_will_reauth');
|
||||
Member::loggedIn()->language()->words['oauth_client_id'] = Member::loggedIn()->language()->addToStack('login_facebook_app');
|
||||
Member::loggedIn()->language()->words['oauth_client_client_secret'] = Member::loggedIn()->language()->addToStack('login_facebook_secret');
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
'real_name' => new \IPS\Helpers\Form\Radio( 'login_real_name', isset( $this->settings['real_name'] ) ? $this->settings['real_name'] : 1, FALSE, array(
|
||||
'real_name' => new Radio( 'login_real_name', $this->settings['real_name'] ?? 1, FALSE, array(
|
||||
'options' => array(
|
||||
1 => 'login_real_name_facebook',
|
||||
0 => 'login_real_name_disabled',
|
||||
@@ -59,25 +76,21 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
), NULL, NULL, NULL, 'login_real_name' ),
|
||||
),
|
||||
parent::acpForm(),
|
||||
array(
|
||||
'allow_status_import' => new \IPS\Helpers\Form\YesNo( 'login_facebook_allow_status_import', ( isset( $this->settings['allow_status_import'] ) ) ? $this->settings['allow_status_import'] : FALSE, FALSE )
|
||||
)
|
||||
array()
|
||||
);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Compatibility
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public static function testCompatibility()
|
||||
public static function testCompatibility(): bool
|
||||
{
|
||||
if ( mb_substr( \IPS\Settings::i()->base_url, 0, 8 ) !== 'https://' )
|
||||
if ( mb_substr( Settings::i()->base_url, 0, 8 ) !== 'https://' )
|
||||
{
|
||||
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( \IPS\CIC ? 'login_facebook_https_cic' : 'login_facebook_https' ) );
|
||||
throw new LogicException( Member::loggedIn()->language()->addToStack( CIC ? 'login_facebook_https_cic' : 'login_facebook_https' ) );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
@@ -88,7 +101,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return '#3a579a';
|
||||
}
|
||||
@@ -98,9 +111,9 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return 'facebook-official';
|
||||
return 'facebook-f';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +121,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return 'login_facebook';
|
||||
}
|
||||
@@ -118,20 +131,20 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_facebook';
|
||||
return 'ipsSocial--facebook';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/Facebook.png', 'core', 'interface' );
|
||||
return Theme::i()->resource( 'logos/login/Facebook.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +152,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
@@ -150,21 +163,16 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
$return = array('email');
|
||||
|
||||
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] )
|
||||
{
|
||||
$return[] = 'user_posts';
|
||||
}
|
||||
|
||||
$additionalPermitted = array( 'manage_pages', 'publish_pages' );
|
||||
if ( $additional !== NULL )
|
||||
{
|
||||
foreach( $additional as $scope )
|
||||
{
|
||||
if ( \in_array( $scope, $additionalPermitted ) )
|
||||
if ( in_array( $scope, $additionalPermitted ) )
|
||||
{
|
||||
$return[] = $scope;
|
||||
}
|
||||
@@ -180,7 +188,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function scopesIssued( $accessToken )
|
||||
public function scopesIssued( string $accessToken ): ?array
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -211,14 +219,14 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
$return = \IPS\Http\Url::external('https://www.facebook.com/dialog/oauth');
|
||||
$return = Url::external('https://www.facebook.com/dialog/oauth');
|
||||
|
||||
if ( $login->type === \IPS\Login::LOGIN_ACP or $login->type === \IPS\Login::LOGIN_REAUTHENTICATE )
|
||||
if ( $login->type === Login::LOGIN_ACP or $login->type === Login::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
$return = $return->setQueryString( 'auth_type', 'reauthenticate' );
|
||||
}
|
||||
@@ -229,23 +237,23 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://graph.facebook.com/v2.9/oauth/access_token');
|
||||
return Url::external('https://graph.facebook.com/v2.9/oauth/access_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirection Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function redirectionEndpoint()
|
||||
protected function redirectionEndpoint(): Url
|
||||
{
|
||||
if ( isset( $this->settings['legacy_redirect'] ) and $this->settings['legacy_redirect'] )
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'applications/core/interface/facebook/auth.php', 'none' );
|
||||
return Url::internal( 'applications/core/interface/facebook/auth.php', 'none' );
|
||||
}
|
||||
return parent::redirectionEndpoint();
|
||||
}
|
||||
@@ -254,9 +262,9 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['id'];
|
||||
}
|
||||
@@ -268,7 +276,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
@@ -284,7 +292,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['email'];
|
||||
}
|
||||
@@ -293,24 +301,24 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $error['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$photoVars = explode( ':', $member->group['g_photo_max_vars'] );
|
||||
$response = $this->_authorizedRequest( "{$link['token_identifier']}/picture?width={$photoVars[1]}&redirect=false", $link['token_access_token'], NULL, 'get' );
|
||||
if ( !$response['data']['is_silhouette'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $response['data']['url'] );
|
||||
return Url::external( $response['data']['url'] );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -319,79 +327,35 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's statuses since a particular date
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param \IPS\DateTime|NULL $since Date/Time to get statuses since then, or NULL to get the latest one
|
||||
* @return array
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userStatuses( \IPS\Member $member, \IPS\DateTime $since = NULL )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$data = array();
|
||||
if ( $since )
|
||||
{
|
||||
$data['since'] = $since->getTimestamp();
|
||||
}
|
||||
|
||||
$return = array();
|
||||
$response = $this->_authorizedRequest( 'me/posts', $link['token_access_token'], $data, 'get' );
|
||||
foreach ( $response['data'] as $statusData )
|
||||
{
|
||||
if( isset( $statusData['message'] ) and !isset( $statusData['story'] ) )
|
||||
{
|
||||
$status = \IPS\core\Statuses\Status::createItem( $member, $member->ip_address, new \IPS\DateTime( $statusData['created_time'] ) );
|
||||
$status->content = $this->_parseStatusText( $member, nl2br( $statusData['message'], FALSE ) );
|
||||
|
||||
$return[] = $status;
|
||||
|
||||
if ( !$since )
|
||||
{
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
$scopes = $this->authorizedScopes( $member );
|
||||
|
||||
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( $scopes and \in_array( 'email', $scopes ) ) )
|
||||
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( $scopes and in_array( 'email', $scopes ) ) )
|
||||
{
|
||||
$return[] = 'email';
|
||||
}
|
||||
@@ -403,28 +367,23 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
$return[] = 'photo';
|
||||
|
||||
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] and ( $scopes and \in_array( 'user_posts', $this->authorizedScopes( $member ) ) ) )
|
||||
{
|
||||
$return[] = 'status';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessToken Access Token
|
||||
* @return array
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
protected function _userData( string $accessToken ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
@@ -435,28 +394,28 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
if ( isset( $response['error'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['error']['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( $response['error']['message'], LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$this->_cachedUserData[ $accessToken ] = $response;
|
||||
}
|
||||
return $this->_cachedUserData[ $accessToken ];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Make authorized request
|
||||
*
|
||||
* @param string $endpoint Endpoint
|
||||
* @param string $accessToken Access Token
|
||||
* @param array|NULL $postData Data to post or query string]
|
||||
* @param string|NULL $method 'get' or 'post'
|
||||
* @param string $endpoint Endpoint
|
||||
* @param string $accessToken Access Token
|
||||
* @param array|null $data Data to post or query string]
|
||||
* @param string|null $method 'get' or 'post'
|
||||
* @return array
|
||||
* @throws \IPS\Http\Request\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _authorizedRequest( $endpoint, $accessToken, $data = NULL, $method = NULL )
|
||||
protected function _authorizedRequest(string $endpoint, string $accessToken, array $data = NULL, string $method = NULL ): array
|
||||
{
|
||||
$url = \IPS\Http\Url::external( "https://graph.facebook.com/{$endpoint}" );
|
||||
$url = Url::external( "https://graph.facebook.com/{$endpoint}" );
|
||||
if ( $method === 'get' and $data )
|
||||
{
|
||||
$url = $url->setQueryString( $data );
|
||||
@@ -474,143 +433,16 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
return $response->decodeJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post something to Facebook
|
||||
*
|
||||
* @param \IPS\Member $member Member posting
|
||||
* @param string $content Content to post
|
||||
* @param \IPS\Http\Url|NULL $url Optional link
|
||||
* @return void
|
||||
*/
|
||||
public function postToFacebook( \IPS\Member $member, $content, \IPS\Http\Url $url = NULL )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$data = array( 'message' => $content );
|
||||
if ( $url !== NULL )
|
||||
{
|
||||
$data['link'] = (string) $url;
|
||||
}
|
||||
|
||||
$response = $this->_authorizedRequest( 'me/feed', $link['token_access_token'], $data );
|
||||
|
||||
return isset( $response['id'] );
|
||||
}
|
||||
|
||||
/* ! Social Promotion */
|
||||
|
||||
/**
|
||||
* Exchange a short lived member token for a longer lived token
|
||||
*
|
||||
* @param string $shortLivedToken The short lived token to exchange for a long lived token
|
||||
* @return array
|
||||
*/
|
||||
public function exchangeMemberToken( $code )
|
||||
{
|
||||
$accessToken = $this->_exchangeAuthorizationCodeForAccessToken( $code );
|
||||
|
||||
/* Get user ID */
|
||||
$accessToken['identifier'] = $this->authenticatedUserId( $accessToken['access_token'] );
|
||||
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange a short lived token for a longer lived token
|
||||
*
|
||||
* @param string $shortLivedToken The short lived token to exchange for a long lived token
|
||||
* @return string
|
||||
*/
|
||||
public function exchangeToken( $shortLivedToken )
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
'grant_type' => 'fb_exchange_token',
|
||||
'fb_exchange_token' => $shortLivedToken
|
||||
) )->decodeJson();
|
||||
|
||||
return isset( $response['access_token'] ) ? $response['access_token'] : NULL;
|
||||
}
|
||||
catch( \RuntimeException $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages this user manages
|
||||
*
|
||||
* @param \IPS\Member $member Member requesting pages
|
||||
* @return array
|
||||
*/
|
||||
public function getPages( $member )
|
||||
{
|
||||
$pages = array();
|
||||
if ( !( $link = $this->_promoteLink( $member ) ) )
|
||||
{
|
||||
return $pages;
|
||||
}
|
||||
|
||||
$response = $this->_authorizedRequest( $link['token_identifier'] . '/accounts?limit=100', $link['token_access_token'], array(
|
||||
'appsecret_proof' => hash_hmac( 'sha256', $link['token_access_token'], $this->settings['client_secret'] )
|
||||
), 'get' );
|
||||
|
||||
if ( !empty( $response['data'] ) )
|
||||
{
|
||||
foreach( $response['data'] as $page )
|
||||
{
|
||||
$pages[ $page['id'] ] = array( $page['name'], $page['access_token'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups this user manages
|
||||
*
|
||||
* @param \IPS\Member $member Member requesting pages
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups( $member )
|
||||
{
|
||||
$groups = array();
|
||||
if ( !( $link = $this->_promoteLink( $member ) ) )
|
||||
{
|
||||
return $groups;
|
||||
}
|
||||
|
||||
$response = $this->_authorizedRequest( $link['token_identifier'] . '/groups', $link['token_access_token'], array(
|
||||
'appsecret_proof' => hash_hmac( 'sha256', $link['token_access_token'], $this->settings['client_secret'] )
|
||||
), 'get' );
|
||||
|
||||
if ( !empty( $response['data'] ) )
|
||||
{
|
||||
foreach( $response['data'] as $group )
|
||||
{
|
||||
$groups[ $group['id'] ] = array( $group['name'], ( isset( $group['privacy'] ) ? $group['privacy'] : NULL ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user link
|
||||
*
|
||||
* @param \IPS\Member $member Member requesting pages
|
||||
* @param Member $member Member requesting pages
|
||||
* @return array
|
||||
*/
|
||||
protected function _promoteLink( $member )
|
||||
protected function _promoteLink( Member $member ): array
|
||||
{
|
||||
if ( \is_numeric( \IPS\Settings::i()->promote_facebook_auth ) )
|
||||
if ( is_numeric( Settings::i()->promote_facebook_auth ) )
|
||||
{
|
||||
/* standard handler */
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
@@ -618,16 +450,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
return array();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$account = \IPS\core\Promote::getPromoter('Facebook')->setMember( $member );
|
||||
$this->settings = json_decode( \IPS\Settings::i()->promote_facebook_auth, TRUE );
|
||||
$link = array(
|
||||
'token_identifier' => $account->settings['member_token']['identifier'],
|
||||
'token_access_token' => $account->settings['member_token']['access_token']
|
||||
);
|
||||
}
|
||||
|
||||
return $link;
|
||||
return $link ?? [];
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,43 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use RuntimeException;
|
||||
use UnderflowException;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Login Handler
|
||||
*/
|
||||
class _Google extends \IPS\Login\Handler\OAuth2
|
||||
class Google extends OAuth2
|
||||
{
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Google';
|
||||
}
|
||||
|
||||
protected static $enableAcpLoginByDefault = FALSE;
|
||||
protected static bool $enableAcpLoginByDefault = FALSE;
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
@@ -42,14 +57,14 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['login_acp_desc'] = \IPS\Member::loggedIn()->language()->addToStack('login_acp_cannot_reauth');
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_id'] = \IPS\Member::loggedIn()->language()->addToStack('login_google_id');
|
||||
Member::loggedIn()->language()->words['login_acp_desc'] = Member::loggedIn()->language()->addToStack('login_acp_cannot_reauth');
|
||||
Member::loggedIn()->language()->words['oauth_client_id'] = Member::loggedIn()->language()->addToStack('login_google_id');
|
||||
|
||||
return array_merge(
|
||||
array(
|
||||
'real_name' => new \IPS\Helpers\Form\Radio( 'login_real_name', isset( $this->settings['real_name'] ) ? $this->settings['real_name'] : 1, FALSE, array(
|
||||
'real_name' => new Radio( 'login_real_name', $this->settings['real_name'] ?? 1, FALSE, array(
|
||||
'options' => array(
|
||||
1 => 'login_real_name_google',
|
||||
0 => 'login_real_name_disabled',
|
||||
@@ -68,7 +83,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return '#4285F4';
|
||||
}
|
||||
@@ -78,7 +93,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return 'google';
|
||||
}
|
||||
@@ -88,7 +103,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return 'login_google';
|
||||
}
|
||||
@@ -98,20 +113,20 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_google';
|
||||
return 'ipsSocial--google';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/Google.png', 'core', 'interface' );
|
||||
return Theme::i()->resource( 'logos/login/Google.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +134,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
@@ -130,27 +145,25 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
$return = array(
|
||||
return array(
|
||||
'profile',
|
||||
'email',
|
||||
);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scopes Issued
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessToken Access Token
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function scopesIssued( $accessToken )
|
||||
public function scopesIssued( string $accessToken ): ?array
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://www.googleapis.com/oauth2/v2/tokeninfo" )
|
||||
$response = Url::external( "https://www.googleapis.com/oauth2/v2/tokeninfo" )
|
||||
->setQueryString( 'access_token', $accessToken )
|
||||
->request()
|
||||
->get()
|
||||
@@ -167,25 +180,25 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
$return = \IPS\Http\Url::external('https://accounts.google.com/o/oauth2/v2/auth?access_type=offline');
|
||||
$return = Url::external('https://accounts.google.com/o/oauth2/v2/auth?access_type=offline');
|
||||
|
||||
if ( $login->type === \IPS\Login::LOGIN_ACP or $login->type === \IPS\Login::LOGIN_REAUTHENTICATE )
|
||||
if ( $login->type === Login::LOGIN_ACP or $login->type === Login::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
$return = $return->setQueryString( 'prompt', 'consent' );
|
||||
}
|
||||
|
||||
if ( $login->type === \IPS\Login::LOGIN_REAUTHENTICATE )
|
||||
if ( $login->type === Login::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
try
|
||||
{
|
||||
$return = $return->setQueryString( 'login_hint', $this->authenticatedEmail( \IPS\Db::i()->select( 'token_access_token', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $login->reauthenticateAs->member_id ) )->first() ) );
|
||||
$return = $return->setQueryString( 'login_hint', $this->authenticatedEmail( Db::i()->select( 'token_access_token', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $login->reauthenticateAs->member_id ) )->first() ) );
|
||||
}
|
||||
catch ( \UnderflowException $e ) {}
|
||||
catch ( UnderflowException $e ) {}
|
||||
}
|
||||
|
||||
return $return;
|
||||
@@ -194,34 +207,34 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://www.googleapis.com/oauth2/v4/token');
|
||||
return Url::external('https://www.googleapis.com/oauth2/v4/token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirection Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function redirectionEndpoint()
|
||||
protected function redirectionEndpoint(): Url
|
||||
{
|
||||
if ( isset( $this->settings['legacy_redirect'] ) and $this->settings['legacy_redirect'] )
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' );
|
||||
return Url::internal( 'applications/core/interface/google/auth.php', 'none' );
|
||||
}
|
||||
return parent::redirectionEndpoint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['sub'];
|
||||
}
|
||||
@@ -233,7 +246,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
@@ -249,7 +262,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['email'];
|
||||
}
|
||||
@@ -258,23 +271,23 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
if ( isset( $userData['picture'] ) and $userData['picture'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['picture'] )->setQueryString( 'sz', NULL );
|
||||
return Url::external( $userData['picture'] )->setQueryString( 'sz', NULL );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -283,17 +296,17 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
@@ -305,12 +318,12 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @param string $identifier The ID Nnumber/string from remote service
|
||||
* @param string $username The username from remote service
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @return Url|NULL
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userLink( $identifier, $username )
|
||||
public function userLink( string $identifier, string $username ): ?Url
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -318,11 +331,11 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$authorizedScopes = $this->authorizedScopes( $member );
|
||||
|
||||
@@ -333,7 +346,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
$return = array();
|
||||
|
||||
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( \in_array( 'email', $authorizedScopes ) or \in_array( 'https://www.googleapis.com/auth/userinfo.email', $authorizedScopes ) ) )
|
||||
if ( ( !isset( $this->settings['update_email_changes'] ) or $this->settings['update_email_changes'] === 'optional' ) and ( in_array( 'email', $authorizedScopes ) or in_array( 'https://www.googleapis.com/auth/userinfo.email', $authorizedScopes ) ) )
|
||||
{
|
||||
$return[] = 'email';
|
||||
}
|
||||
@@ -351,20 +364,20 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
protected function _userData( string $accessToken ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://www.googleapis.com/oauth2/v3/userinfo" )
|
||||
$response = Url::external( "https://www.googleapis.com/oauth2/v3/userinfo" )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
@@ -376,12 +389,12 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( isset( $response['error_description'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['error_description'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( $response['error_description'], Exception::INTERNAL_ERROR );
|
||||
}
|
||||
// Keeping this for backwards compatibility..
|
||||
else if( isset( $response['error']['errors'][0]['message'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['error']['errors'][0]['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( $response['error']['errors'][0]['message'], Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@
|
||||
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
class _InitException extends \Exception { }
|
||||
use Exception;
|
||||
|
||||
class InitException extends Exception { }
|
||||
@@ -11,28 +11,49 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use IPS\File;
|
||||
use IPS\Helpers\Form\Color;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Helpers\Form\Upload;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception as LoginException;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invision Community Login Handler
|
||||
*/
|
||||
class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
class Invision extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @brief Can we have multiple instances of this handler?
|
||||
*/
|
||||
public static $allowMultiple = TRUE;
|
||||
public static bool $allowMultiple = TRUE;
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_InvisionCommunity';
|
||||
}
|
||||
@@ -45,21 +66,21 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['login_acp_desc'] = \IPS\Member::loggedIn()->language()->addToStack('login_acp_will_reauth');
|
||||
Member::loggedIn()->language()->words['login_acp_desc'] = Member::loggedIn()->language()->addToStack('login_acp_will_reauth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return[] = array( 'login_handler_InvisionCommunity_info_title', 'login_handler_InvisionCommunity_info' );
|
||||
$return['url'] = new \IPS\Helpers\Form\Url( 'oauth_invision_endpoint', isset( $this->settings['url'] ) ? $this->settings['url'] : NULL, TRUE, array( 'placeholder' => 'https://othercommunity.example.com' ), function( $val )
|
||||
$return['url'] = new \IPS\Helpers\Form\Url( 'oauth_invision_endpoint', $this->settings['url'] ?? NULL, TRUE, array( 'placeholder' => 'https://othercommunity.example.com' ), function( $val )
|
||||
{
|
||||
if ( rtrim( (string) $val, '/' ) === rtrim( \IPS\Settings::i()->base_url, '/' ) )
|
||||
if ( rtrim( (string) $val, '/' ) === rtrim( Settings::i()->base_url, '/' ) )
|
||||
{
|
||||
throw new \DomainException('oauth_invision_endpoint_internal');
|
||||
throw new DomainException('oauth_invision_endpoint_internal');
|
||||
}
|
||||
} );
|
||||
$return['grant_type'] = new \IPS\Helpers\Form\Radio( 'oauth_invision_grant_type', isset( $this->settings['grant_type'] ) ? $this->settings['grant_type'] : 'authorization_code', TRUE, array(
|
||||
$return['grant_type'] = new Radio( 'oauth_invision_grant_type', $this->settings['grant_type'] ?? 'authorization_code', TRUE, array(
|
||||
'options' => array(
|
||||
'authorization_code' => 'invision_grant_type_authorization_code',
|
||||
'password' => 'invision_grant_type_password',
|
||||
@@ -77,21 +98,21 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$active = 'accountManagementSettings';
|
||||
}
|
||||
if ( !\is_string( $v ) and !\is_array( $v ) )
|
||||
if ( !is_string( $v ) and !is_array( $v ) )
|
||||
{
|
||||
${$active}[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$return[] = 'login_handler_oauth_ui';
|
||||
$return['auth_types'] = new \IPS\Helpers\Form\Select( 'oauth_custom_auth_types', isset( $this->settings['auth_types'] ) ? $this->settings['auth_types'] : ( \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
$return['auth_types'] = new Select( 'oauth_custom_auth_types', $this->settings['auth_types'] ?? ( Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
Login::AUTH_TYPE_USERNAME => 'username',
|
||||
) ), NULL, NULL, NULL, 'oauth_custom_auth_types' );
|
||||
$return['button_color'] = new \IPS\Helpers\Form\Color( 'oauth_custom_button_color', isset( $this->settings['button_color'] ) ? $this->settings['button_color'] : '#3E4148', NULL, array(), NULL, NULL, NULL, 'button_color' );
|
||||
$return['button_text'] = new \IPS\Helpers\Form\Translatable( 'oauth_custom_button_text', NULL, NULL, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('oauth_custom_button_text_invision_placeholder'), 'app' => 'core', 'key' => ( $this->id ? "core_custom_oauth_{$this->id}" : NULL ) ), NULL, NULL, NULL, 'button_text' );
|
||||
$return['button_icon'] = new \IPS\Helpers\Form\Upload( 'oauth_custom_button_icon', ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] ) : NULL, FALSE, array( 'storageExtension' => 'core_Login' ), NULL, NULL, NULL, 'button_icon' );
|
||||
$return['button_color'] = new Color( 'oauth_custom_button_color', $this->settings['button_color'] ?? '#3E4148', NULL, array(), NULL, NULL, NULL, 'button_color' );
|
||||
$return['button_text'] = new Translatable( 'oauth_custom_button_text', NULL, NULL, array( 'placeholder' => Member::loggedIn()->language()->addToStack('oauth_custom_button_text_invision_placeholder'), 'app' => 'core', 'key' => ( $this->id ? "core_custom_oauth_{$this->id}" : NULL ) ), NULL, NULL, NULL, 'button_text' );
|
||||
$return['button_icon'] = new Upload( 'oauth_custom_button_icon', ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] ) : NULL, FALSE, array( 'storageExtension' => 'core_Login' ), NULL, NULL, NULL, 'button_icon' );
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
foreach ( $accountManagementSettings as $k => $v )
|
||||
@@ -108,7 +129,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
public function acpFormSave( array &$values ): array
|
||||
{
|
||||
$return = parent::acpFormSave( $values );
|
||||
$return['url'] = (string) $return['url'];
|
||||
@@ -122,7 +143,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
if( isset( $values['oauth_custom_button_text'] ) )
|
||||
{
|
||||
@@ -130,7 +151,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
\IPS\Lang::saveCustom( 'core', "core_custom_oauth_{$this->id}", $values['oauth_custom_button_text'] );
|
||||
Lang::saveCustom( 'core', "core_custom_oauth_{$this->id}", $values['oauth_custom_button_text'] );
|
||||
unset( $values['button_text'] );
|
||||
}
|
||||
|
||||
@@ -142,7 +163,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return $this->settings['button_color'];
|
||||
}
|
||||
@@ -152,9 +173,9 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] ) : NULL;
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,28 +183,28 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_ips';
|
||||
return 'ipsSocial--ips';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] )->url : NULL;
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] )->url : NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get logo to display in user cp sidebar
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForUcp()
|
||||
public function logoForUcp(): Url|string|null
|
||||
{
|
||||
return $this->logoForDeviceInformation();
|
||||
}
|
||||
@@ -193,7 +214,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return "core_custom_oauth_{$this->id}";
|
||||
}
|
||||
@@ -203,7 +224,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
protected function _authenticationType(): string
|
||||
{
|
||||
return static::AUTHENTICATE_POST; // Just because it's possible their server isn't configured to accept HTTP Authorization whereas we know this will always work
|
||||
}
|
||||
@@ -213,9 +234,9 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return isset( $this->settings['grant_type'] ) ? $this->settings['grant_type'] : 'authorization_code';
|
||||
return $this->settings['grant_type'] ?? 'authorization_code';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +245,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
return array( 'profile', 'email' );
|
||||
}
|
||||
@@ -232,14 +253,14 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
$return = \IPS\Http\Url::external( $this->settings['url'] . '/oauth/authorize/' );
|
||||
$return = Url::external( $this->settings['url'] . '/oauth/authorize/' );
|
||||
|
||||
if ( $login->type === \IPS\Login::LOGIN_ACP or $login->type === \IPS\Login::LOGIN_REAUTHENTICATE )
|
||||
if ( $login->type === Login::LOGIN_ACP or $login->type === Login::LOGIN_REAUTHENTICATE )
|
||||
{
|
||||
$return = $return->setQueryString( 'prompt', 'login' );
|
||||
}
|
||||
@@ -250,20 +271,20 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external( $this->settings['url'] . '/oauth/token/' );
|
||||
return Url::external( $this->settings['url'] . '/oauth/token/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
$userData = $this->_userData( $accessToken );
|
||||
if ( isset( $userData['id'] ) )
|
||||
@@ -280,7 +301,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
$userData = $this->_userData( $accessToken );
|
||||
if ( isset( $userData['name'] ) )
|
||||
@@ -297,7 +318,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
$userData = $this->_userData( $accessToken );
|
||||
if ( isset( $userData['email'] ) )
|
||||
@@ -311,23 +332,23 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
if ( ( !isset( $userData['photoUrlIsDefault'] ) or !$userData['photoUrlIsDefault'] ) AND isset( $userData['photoUrl'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['photoUrl'] );
|
||||
return Url::external( $userData['photoUrl'] );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -336,17 +357,17 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
@@ -362,23 +383,23 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's cover photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userCoverPhoto( \IPS\Member $member )
|
||||
public function userCoverPhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( "", LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
if ( isset( $userData['coverPhotoUrl'] ) and $userData['coverPhotoUrl'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['coverPhotoUrl'] );
|
||||
return Url::external( $userData['coverPhotoUrl'] );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -390,24 +411,24 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @param string $identifier The ID Nnumber/string from remote service
|
||||
* @param string $username The username from remote service
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @return Url|NULL
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userLink( $identifier, $username )
|
||||
public function userLink( string $identifier, string $username ): ?Url
|
||||
{
|
||||
return \IPS\Http\Url::external( $this->settings['url'] )->setQueryString( 'showuser', $identifier );
|
||||
return Url::external( $this->settings['url'] )->setQueryString( 'showuser', $identifier );
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -430,21 +451,21 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @return array|null
|
||||
* @return array
|
||||
*@throws RuntimeException Unexpected error from service
|
||||
* @throws LoginException The token is invalid and the user needs to reauthenticate
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
protected function _userData( string $accessToken ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( $this->settings['url'] . '/api/index.php?/core/me' )
|
||||
$response = Url::external( $this->settings['url'] . '/api/index.php?/core/me' )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
@@ -454,12 +475,12 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
if ( isset( $response['errorCode'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['errorMessage'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new LoginException( $response['errorMessage'], LoginException::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$email = \IPS\Http\Url::external( $this->settings['url'] . '/api/index.php?/core/me/email' )
|
||||
$email = Url::external( $this->settings['url'] . '/api/index.php?/core/me/email' )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
@@ -472,7 +493,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
$response['email'] = $email['email'];
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
catch ( Exception $e ) { }
|
||||
|
||||
$this->_cachedUserData[ $accessToken ] = $response;
|
||||
}
|
||||
@@ -482,10 +503,10 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Forgot Password URL
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forgotPasswordUrl()
|
||||
public function forgotPasswordUrl(): ?Url
|
||||
{
|
||||
return \IPS\Http\Url::external( $this->settings['url'] . '/index.php?app=core&module=system&controller=lostpass' );
|
||||
return Url::external( $this->settings['url'] . '/index.php?app=core&module=system&controller=lostpass' );
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,40 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* LinkedIn Login Handler
|
||||
*/
|
||||
class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
class LinkedIn extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @brief Does this handler support PKCE?
|
||||
*/
|
||||
public $pkceSupported = FALSE;
|
||||
public bool $pkceSupported = FALSE;
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Linkedin';
|
||||
}
|
||||
@@ -40,7 +52,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Enable AdminCP logins by default
|
||||
*/
|
||||
protected static $enableAcpLoginByDefault = FALSE;
|
||||
protected static bool $enableAcpLoginByDefault = FALSE;
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
@@ -50,13 +62,13 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['login_acp_desc'] = \IPS\Member::loggedIn()->language()->addToStack('login_acp_cannot_reauth');
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_id'] = \IPS\Member::loggedIn()->language()->addToStack('login_linkedin_key');
|
||||
Member::loggedIn()->language()->words['login_acp_desc'] = Member::loggedIn()->language()->addToStack('login_acp_cannot_reauth');
|
||||
Member::loggedIn()->language()->words['oauth_client_id'] = Member::loggedIn()->language()->addToStack('login_linkedin_key');
|
||||
|
||||
return array_merge( array(
|
||||
'real_name' => new \IPS\Helpers\Form\Radio( 'login_real_name', isset( $this->settings['real_name'] ) ? $this->settings['real_name'] : 1, FALSE, array(
|
||||
'real_name' => new Radio( 'login_real_name', $this->settings['real_name'] ?? 1, FALSE, array(
|
||||
'options' => array(
|
||||
1 => 'login_real_name_linkedin',
|
||||
0 => 'login_real_name_disabled',
|
||||
@@ -73,7 +85,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return '#007eb3';
|
||||
}
|
||||
@@ -83,7 +95,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return 'linkedin';
|
||||
}
|
||||
@@ -93,7 +105,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return 'login_linkedin';
|
||||
}
|
||||
@@ -103,20 +115,20 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_linkedin';
|
||||
return 'ipsSocial--linkedIn';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/Linkedin.png', 'core', 'interface' );
|
||||
return Theme::i()->resource( 'logos/login/Linkedin.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +136,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
protected function _authenticationType(): string
|
||||
{
|
||||
return static::AUTHENTICATE_POST;
|
||||
}
|
||||
@@ -134,7 +146,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
@@ -145,7 +157,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
return array(
|
||||
'r_liteprofile',
|
||||
@@ -156,34 +168,34 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://www.linkedin.com/oauth/v2/authorization');
|
||||
return Url::external('https://www.linkedin.com/oauth/v2/authorization');
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://www.linkedin.com/oauth/v2/accessToken');
|
||||
return Url::external('https://www.linkedin.com/oauth/v2/accessToken');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirection Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function redirectionEndpoint()
|
||||
protected function redirectionEndpoint(): Url
|
||||
{
|
||||
if ( isset( $this->settings['legacy_redirect'] ) and $this->settings['legacy_redirect'] )
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'applications/core/interface/linkedin/auth.php', 'none' );
|
||||
return Url::internal( 'applications/core/interface/linkedin/auth.php', 'none' );
|
||||
}
|
||||
return parent::redirectionEndpoint();
|
||||
}
|
||||
@@ -192,9 +204,9 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['id'];
|
||||
}
|
||||
@@ -206,7 +218,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
@@ -218,7 +230,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached email address to make sure we don't request more than once
|
||||
*/
|
||||
protected $_cachedEmail = NULL;
|
||||
protected ?string $_cachedEmail = NULL;
|
||||
|
||||
/**
|
||||
* Get authenticated user's email address
|
||||
@@ -227,11 +239,11 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
if ( $this->_cachedEmail === NULL )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))" )
|
||||
$response = Url::external( "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))" )
|
||||
->request()
|
||||
->setHeaders( array( 'Authorization' => "Bearer {$accessToken}" ) )
|
||||
->get()
|
||||
@@ -239,7 +251,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
if ( isset( $response['errorCode'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( $response['message'], Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$this->_cachedEmail = $response['elements'][0]['handle~']['emailAddress'];
|
||||
@@ -251,17 +263,17 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if( !isset( $this->_userData( $link['token_access_token'] )['profilePicture'] ) )
|
||||
@@ -279,7 +291,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if( isset( $identifier['identifier'] ) AND $identifier['identifier'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $identifier['identifier'] );
|
||||
return Url::external( $identifier['identifier'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,31 +303,29 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->_getProfileName( $link['token_access_token'] );
|
||||
|
||||
$preferredLocale = $this->_userData( $link['token_access_token'] )['firstName']['preferredLocale']['language'] . '_' . $this->_userData( $link['token_access_token'] )['firstName']['preferredLocale']['country'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's profile name
|
||||
*
|
||||
* @param string $accessToken Access token
|
||||
* @param string $accessToken Access token
|
||||
* @return string
|
||||
*/
|
||||
protected function _getProfileName( $accessToken )
|
||||
protected function _getProfileName( string $accessToken ): string
|
||||
{
|
||||
$preferredLocale = $this->_userData( $accessToken )['firstName']['preferredLocale']['language'] . '_' . $this->_userData( $accessToken )['firstName']['preferredLocale']['country'];
|
||||
|
||||
@@ -328,13 +338,13 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @param string $identifier The ID Number/string from remote service
|
||||
* @param string $username The username from remote service
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @return Url|NULL
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
* @note You have to apply to LinkedIn's Partnership program manually to get the profile data necessary to generate the public profile URL (specifically, the vanityName field). As such, we can't generate the URL to your profile beginning with the v2 API.
|
||||
*/
|
||||
public function userLink( $identifier, $username )
|
||||
public function userLink( string $identifier, string $username ): ?Url
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -342,11 +352,11 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -369,20 +379,20 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
protected function _userData( string $accessToken ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://api.linkedin.com/v2/me" )
|
||||
$response = Url::external( "https://api.linkedin.com/v2/me" )
|
||||
->setQueryString( 'projection', '(id,firstName,lastName,vanityName,profilePicture(displayImage~:playableStreams))' )
|
||||
->request()
|
||||
->setHeaders( array( 'Authorization' => "Bearer {$accessToken}" ) )
|
||||
@@ -391,7 +401,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
if ( isset( $response['errorCode'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( $response['message'], Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$this->_cachedUserData[ $accessToken ] = $response;
|
||||
|
||||
@@ -11,28 +11,40 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Microsoft Login Handler
|
||||
*/
|
||||
class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
class Microsoft extends OAuth2
|
||||
{
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Live';
|
||||
}
|
||||
|
||||
protected static $enableAcpLoginByDefault = FALSE;
|
||||
protected static bool $enableAcpLoginByDefault = FALSE;
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
@@ -42,14 +54,14 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['login_acp_desc'] = \IPS\Member::loggedIn()->language()->addToStack('login_acp_cannot_reauth');
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_id'] = \IPS\Member::loggedIn()->language()->addToStack('login_live_client');
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_client_secret'] = \IPS\Member::loggedIn()->language()->addToStack('login_live_secret');
|
||||
Member::loggedIn()->language()->words['login_acp_desc'] = Member::loggedIn()->language()->addToStack('login_acp_cannot_reauth');
|
||||
Member::loggedIn()->language()->words['oauth_client_id'] = Member::loggedIn()->language()->addToStack('login_live_client');
|
||||
Member::loggedIn()->language()->words['oauth_client_client_secret'] = Member::loggedIn()->language()->addToStack('login_live_secret');
|
||||
|
||||
return array_merge( array(
|
||||
'real_name' => new \IPS\Helpers\Form\Radio( 'login_real_name', isset( $this->settings['real_name'] ) ? $this->settings['real_name'] : 1, FALSE, array(
|
||||
'real_name' => new Radio( 'login_real_name', $this->settings['real_name'] ?? 1, FALSE, array(
|
||||
'options' => array(
|
||||
1 => 'login_real_name_microsoft',
|
||||
0 => 'login_real_name_disabled',
|
||||
@@ -66,7 +78,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return '#008b00';
|
||||
}
|
||||
@@ -76,7 +88,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return 'windows';
|
||||
}
|
||||
@@ -86,7 +98,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return 'login_live';
|
||||
}
|
||||
@@ -96,20 +108,20 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
public function buttonClass(): string
|
||||
{
|
||||
return 'ipsSocial_microsoft';
|
||||
return 'ipsSocial--microsoft';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/Microsoft.png', 'core', 'interface' );
|
||||
return Theme::i()->resource( 'logos/login/Microsoft.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +129,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
@@ -128,7 +140,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
return array(
|
||||
'openid',
|
||||
@@ -140,45 +152,45 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
|
||||
return Url::external('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external('https://login.microsoftonline.com/common/oauth2/v2.0/token');
|
||||
return Url::external('https://login.microsoftonline.com/common/oauth2/v2.0/token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirection Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function redirectionEndpoint()
|
||||
protected function redirectionEndpoint(): Url
|
||||
{
|
||||
if ( isset( $this->settings['legacy_redirect'] ) and $this->settings['legacy_redirect'] )
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'applications/core/interface/microsoft/auth.php', 'none' );
|
||||
return Url::internal( 'applications/core/interface/microsoft/auth.php', 'none' );
|
||||
}
|
||||
return parent::redirectionEndpoint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['id'];
|
||||
}
|
||||
@@ -190,7 +202,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
@@ -206,7 +218,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
return $this->_userData( $accessToken )['userPrincipalName'];
|
||||
}
|
||||
@@ -215,17 +227,17 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['displayName'];
|
||||
@@ -234,11 +246,11 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -258,20 +270,20 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
protected function _userData( string $accessToken ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://graph.microsoft.com/v1.0/me" )
|
||||
$response = Url::external( "https://graph.microsoft.com/v1.0/me" )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
@@ -281,7 +293,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
if ( isset( $response['error'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['error']['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( $response['error']['message'], Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$this->_cachedUserData[ $accessToken ] = $response;
|
||||
|
||||
@@ -12,43 +12,65 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Radio;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Http\Response;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Log;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use UnderflowException;
|
||||
use function defined;
|
||||
use function intval;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract OAuth2 Login Handler
|
||||
*/
|
||||
abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
abstract class OAuth2 extends Handler
|
||||
{
|
||||
/* !Login Handler: Basics */
|
||||
|
||||
/**
|
||||
* @brief Any additional scopes to authenticate with
|
||||
*/
|
||||
public $additionalScopes = NULL;
|
||||
public mixed $additionalScopes = NULL;
|
||||
|
||||
/**
|
||||
* @brief Does this handler support PKCE?
|
||||
*/
|
||||
public $pkceSupported = TRUE;
|
||||
public bool $pkceSupported = TRUE;
|
||||
|
||||
/**
|
||||
* Get type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function type()
|
||||
public function type(): int
|
||||
{
|
||||
if ( $this->grantType() === 'password' )
|
||||
{
|
||||
return \IPS\Login::TYPE_USERNAME_PASSWORD;
|
||||
return Login::TYPE_USERNAME_PASSWORD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Login::TYPE_BUTTON;
|
||||
return Login::TYPE_BUTTON;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,16 +82,16 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$return = array(
|
||||
array( 'login_handler_oauth_settings', \IPS\Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
|
||||
'client_id' => new \IPS\Helpers\Form\Text( 'oauth_client_id', isset( $this->settings['client_id'] ) ? $this->settings['client_id'] : NULL, TRUE ),
|
||||
'client_secret' => new \IPS\Helpers\Form\Text( 'oauth_client_client_secret', isset( $this->settings['client_secret'] ) ? $this->settings['client_secret'] : NULL, NULL, array(), NULL, NULL, NULL, 'client_secret' ),
|
||||
array( 'login_handler_oauth_settings', Member::loggedIn()->language()->addToStack( static::getTitle() . '_info', FALSE, array( 'sprintf' => array( (string) $this->redirectionEndpoint() ) ) ) ),
|
||||
'client_id' => new Text( 'oauth_client_id', $this->settings['client_id'] ?? NULL, TRUE ),
|
||||
'client_secret' => new Text( 'oauth_client_client_secret', $this->settings['client_secret'] ?? NULL, NULL, array(), NULL, NULL, NULL, 'client_secret' ),
|
||||
);
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
$return['show_in_ucp'] = new \IPS\Helpers\Form\Radio( 'login_handler_show_in_ucp', isset( $this->settings['show_in_ucp'] ) ? $this->settings['show_in_ucp'] : 'always', FALSE, array(
|
||||
$return['show_in_ucp'] = new Radio( 'login_handler_show_in_ucp', $this->settings['show_in_ucp'] ?? 'always', FALSE, array(
|
||||
'options' => array(
|
||||
'always' => 'login_handler_show_in_ucp_always',
|
||||
'loggedin' => 'login_handler_show_in_ucp_loggedin',
|
||||
@@ -81,9 +103,9 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
if ( $forceNameHandler = static::handlerHasForceSync( 'name', $this ) )
|
||||
{
|
||||
$nameChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_name_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceNameHandler->_title ) );
|
||||
}
|
||||
$return['update_name_changes'] = new \IPS\Helpers\Form\Radio( 'login_update_name_changes', isset( $this->settings['update_name_changes'] ) ? $this->settings['update_name_changes'] : 'disabled', FALSE, array( 'options' => array(
|
||||
$return['update_name_changes'] = new Radio( 'login_update_name_changes', $this->settings['update_name_changes'] ?? 'disabled', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
@@ -93,9 +115,9 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
if ( $forceEmailHandler = static::handlerHasForceSync( 'email', $this ) )
|
||||
{
|
||||
$emailChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
}
|
||||
$return['update_email_changes'] = new \IPS\Helpers\Form\Radio( 'login_update_email_changes', isset( $this->settings['update_email_changes'] ) ? $this->settings['update_email_changes'] : 'optional', FALSE, array( 'options' => array(
|
||||
$return['update_email_changes'] = new Radio( 'login_update_email_changes', $this->settings['update_email_changes'] ?? 'optional', FALSE, array( 'options' => array(
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
@@ -108,9 +130,9 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* Test Settings
|
||||
*
|
||||
* @return bool
|
||||
* @throws \LogicException
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function testSettings()
|
||||
public function testSettings(): bool
|
||||
{
|
||||
parent::testSettings();
|
||||
|
||||
@@ -127,7 +149,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
if ( isset( $response['error'] ) and $response['error'] === 'invalid_client' )
|
||||
{
|
||||
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth_setup_error_secret' ) );
|
||||
throw new LogicException( Member::loggedIn()->language()->addToStack( 'oauth_setup_error_secret' ) );
|
||||
}
|
||||
}
|
||||
/* Password */
|
||||
@@ -141,14 +163,16 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
if ( !isset( $response['error'] ) or $response['error'] !== 'invalid_grant' )
|
||||
{
|
||||
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth_setup_error_generic', FALSE, array( 'sprintf' => array( isset( $response['error_description'] ) ? $response['error_description'] : NULL ) ) ) );
|
||||
throw new LogicException( Member::loggedIn()->language()->addToStack( 'oauth_setup_error_generic', FALSE, array( 'sprintf' => array( $response['error_description'] ?? NULL ) ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \LogicException( \IPS\Member::loggedIn()->language()->addToStack( 'oauth_setup_error_generic', FALSE, array( 'sprintf' => array( $e->getMessage() ) ) ) );
|
||||
throw new LogicException( Member::loggedIn()->language()->addToStack( 'oauth_setup_error_generic', FALSE, array( 'sprintf' => array( $e->getMessage() ) ) ) );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* !Button Authentication */
|
||||
@@ -158,20 +182,20 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @return Member|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticateButton( \IPS\Login $login )
|
||||
public function authenticateButton( Login $login ): ?Member
|
||||
{
|
||||
/* If we have a code, process it */
|
||||
if ( $this->grantType() === 'authorization_code' and ( isset( \IPS\Request::i()->code ) or isset( \IPS\Request::i()->error ) ) )
|
||||
if ( $this->grantType() === 'authorization_code' and ( isset( Request::i()->code ) or isset( Request::i()->error ) ) )
|
||||
{
|
||||
return $this->_handleAuthorizationResponse( $login );
|
||||
}
|
||||
|
||||
/* If we have a token, process that */
|
||||
elseif ( $this->grantType() === 'implicit' and ( isset( \IPS\Request::i()->access_token ) or isset( \IPS\Request::i()->error ) ) )
|
||||
elseif ( $this->grantType() === 'implicit' and ( isset( Request::i()->access_token ) or isset( Request::i()->error ) ) )
|
||||
{
|
||||
return $this->_handleAuthorizationResponse( $login );
|
||||
}
|
||||
@@ -183,25 +207,25 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
'client_id' => $this->settings['client_id'],
|
||||
'response_type' => $this->grantType() === 'authorization_code' ? 'code' : 'token',
|
||||
'redirect_uri' => (string) $this->redirectionEndpoint(),
|
||||
'state' => $this->id . '-' . base64_encode( $login->url ) . '-' . \IPS\Session::i()->csrfKey . '-' . \IPS\Request::i()->ref,
|
||||
'state' => $this->id . '-' . base64_encode( $login->url ) . '-' . Session::i()->csrfKey . '-' . Request::i()->ref,
|
||||
);
|
||||
|
||||
if ( $this->grantType() === 'authorization_code' AND $this->pkceSupported === TRUE )
|
||||
{
|
||||
$codeChallenge = \IPS\Login::generateRandomString( 128 );
|
||||
\IPS\Request::i()->setCookie('codeVerifier', $codeChallenge, ( new \IPS\DateTime )->add( new \DateInterval( 'PT10M' ) ) );
|
||||
$codeChallenge = Login::generateRandomString( 128 );
|
||||
Request::i()->setCookie('codeVerifier', $codeChallenge, ( new DateTime )->add( new \DateInterval( 'PT10M' ) ) );
|
||||
$data['code_challenge'] = rtrim( strtr( base64_encode( pack( 'H*', hash( 'sha256', $codeChallenge ) ) ), '+/', '-_' ), '=' );
|
||||
$data['code_challenge_method'] = 'S256';
|
||||
}
|
||||
|
||||
$target = $this->authorizationEndpoint( $login )->setQueryString( $data );
|
||||
|
||||
if ( $scopes = $this->scopesToRequest( isset( \IPS\Request::i()->scopes ) ? explode( ',', \IPS\Request::i()->scopes ) : NULL ) )
|
||||
if ( $scopes = $this->scopesToRequest( isset( Request::i()->scopes ) ? explode( ',', Request::i()->scopes ) : NULL ) )
|
||||
{
|
||||
$target = $target->setQueryString( 'scope', implode( ' ', $scopes ) );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( $target );
|
||||
Output::i()->redirect( $target );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,25 +236,22 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
|
||||
public function authenticateUsernamePassword( Login $login, string $usernameOrEmail, object $password ): Member
|
||||
{
|
||||
if( !$usernameOrEmail )
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$member = new \IPS\Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
}
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_bad_username_or_password', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::NO_ACCOUNT, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_bad_username_or_password', FALSE ), Exception::NO_ACCOUNT, NULL, $member );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
@@ -249,8 +270,8 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'oauth' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'oauth' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( isset( $accessToken['access_token'] ) )
|
||||
@@ -263,62 +284,39 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$member = new \IPS\Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
}
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_bad_username_or_password', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::NO_ACCOUNT, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_bad_username_or_password', FALSE ), Exception::NO_ACCOUNT, NULL, $member );
|
||||
}
|
||||
|
||||
\IPS\Log::log( print_r( $accessToken, TRUE ), 'oauth' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( print_r( $accessToken, TRUE ), 'oauth' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticatePasswordForMember( \IPS\Member $member, $password )
|
||||
public function authenticatePasswordForMember( Member $member, object $password ): bool
|
||||
{
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
try
|
||||
{
|
||||
try
|
||||
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
'grant_type' => 'password',
|
||||
'username' => $member->email,
|
||||
'password' => (string) $password,
|
||||
) )->decodeJson();
|
||||
if ( isset( $response['access_token'] ) )
|
||||
{
|
||||
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
'grant_type' => 'password',
|
||||
'username' => $member->name,
|
||||
'password' => (string) $password,
|
||||
) )->decodeJson();
|
||||
if ( isset( $response['access_token'] ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
try
|
||||
{
|
||||
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
'grant_type' => 'password',
|
||||
'username' => $member->email,
|
||||
'password' => (string) $password,
|
||||
) )->decodeJson();
|
||||
if ( isset( $response['access_token'] ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
@@ -333,18 +331,19 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
protected function _authenticationType(): string
|
||||
{
|
||||
return static::AUTHENTICATE_HEADER;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send request authenticated with client credentials
|
||||
*
|
||||
* @param \IPS\Http\Url $url The URL
|
||||
* @return \IPS\Http\Response
|
||||
* @param Url $url The URL
|
||||
* @param array $data
|
||||
* @return Response
|
||||
*/
|
||||
protected function _authenticatedRequest( \IPS\Http\Url $url, $data )
|
||||
protected function _authenticatedRequest( Url $url, array $data ): Response
|
||||
{
|
||||
$request = $url->request();
|
||||
|
||||
@@ -364,40 +363,40 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Handle authorization response
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @return Member|null
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _handleAuthorizationResponse( \IPS\Login $login )
|
||||
protected function _handleAuthorizationResponse( Login $login ): ?Member
|
||||
{
|
||||
/* Did we get an error? */
|
||||
if ( isset( \IPS\Request::i()->error ) )
|
||||
if ( isset( Request::i()->error ) )
|
||||
{
|
||||
if ( \IPS\Request::i()->error === 'access_denied' )
|
||||
if ( Request::i()->error === 'access_denied' )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Log::log( print_r( $_GET, TRUE ), 'oauth' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( print_r( $_GET, TRUE ), 'oauth' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have a code, swap it for an access token, otherwise, decode what we have */
|
||||
if ( isset( \IPS\Request::i()->code ) )
|
||||
if ( isset( Request::i()->code ) )
|
||||
{
|
||||
$accessToken = $this->_exchangeAuthorizationCodeForAccessToken( \IPS\Request::i()->code );
|
||||
$accessToken = $this->_exchangeAuthorizationCodeForAccessToken( Request::i()->code );
|
||||
}
|
||||
else
|
||||
{
|
||||
$accessToken = array(
|
||||
'access_token' => \IPS\Request::i()->access_token,
|
||||
'token_type' => isset( \IPS\Request::i()->token_type ) ? \IPS\Request::i()->token_type : 'bearer'
|
||||
'access_token' => Request::i()->access_token,
|
||||
'token_type' => isset( Request::i()->token_type ) ? Request::i()->token_type : 'bearer'
|
||||
);
|
||||
if ( isset( \IPS\Request::i()->expires_in ) )
|
||||
if ( isset( Request::i()->expires_in ) )
|
||||
{
|
||||
$accessToken['expires_in'] = \IPS\Request::i()->expires_in;
|
||||
$accessToken['expires_in'] = Request::i()->expires_in;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,12 +407,12 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Process an Access Token
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param array $accessToken Access Token
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param array $accessToken Access Token
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _processAccessToken( \IPS\Login $login, $accessToken )
|
||||
protected function _processAccessToken( Login $login, array $accessToken ): Member
|
||||
{
|
||||
/* Get user id */
|
||||
try
|
||||
@@ -422,8 +421,8 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'oauth' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( $e, 'oauth' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* What scopes did we get? */
|
||||
@@ -439,20 +438,20 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/* Has this user signed in with this service before? */
|
||||
try
|
||||
{
|
||||
$oauthAccess = \IPS\Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $userId ) )->first();
|
||||
$member = \IPS\Member::load( $oauthAccess['token_member'] );
|
||||
$oauthAccess = Db::i()->select( '*', 'core_login_links', array( 'token_login_method=? AND token_identifier=?', $this->id, $userId ) )->first();
|
||||
$member = Member::load( $oauthAccess['token_member'] );
|
||||
|
||||
/* If the user never finished the linking process, or the account has been deleted, discard this access token */
|
||||
if ( !$oauthAccess['token_linked'] or !$member->member_id )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
throw new \UnderflowException;
|
||||
Db::i()->delete( 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
throw new UnderflowException;
|
||||
}
|
||||
|
||||
/* Otherwise, update our token without replacing values already set but not reset in this request... */
|
||||
$update = array(
|
||||
'token_access_token' => $accessToken['access_token'],
|
||||
'token_expires' => ( isset( $accessToken['expires_in'] ) ) ? ( time() + \intval( $accessToken['expires_in'] ) ) : NULL
|
||||
'token_expires' => ( isset( $accessToken['expires_in'] ) ) ? ( time() + intval( $accessToken['expires_in'] ) ) : NULL
|
||||
);
|
||||
|
||||
if( isset( $accessToken['refresh_token'] ) )
|
||||
@@ -465,13 +464,13 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
$update['token_scope'] = json_encode( $scope );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->update( 'core_login_links', $update, array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
Db::i()->update( 'core_login_links', $update, array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
|
||||
/* ... and return the member object */
|
||||
return $member;
|
||||
}
|
||||
/* No, create or link the account */
|
||||
catch ( \UnderflowException $e )
|
||||
catch ( UnderflowException $e )
|
||||
{
|
||||
/* Get the username + email */
|
||||
$name = NULL;
|
||||
@@ -490,9 +489,9 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
try
|
||||
{
|
||||
if ( $login->type === \IPS\Login::LOGIN_UCP )
|
||||
if ( $login->type === Login::LOGIN_UCP )
|
||||
{
|
||||
$exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception = new Exception( 'generic_error', Exception::MERGE_SOCIAL_ACCOUNT );
|
||||
$exception->handler = $this;
|
||||
$exception->member = $login->reauthenticateAs;
|
||||
throw $exception;
|
||||
@@ -500,14 +499,14 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
$member = $this->createAccount( $name, $email );
|
||||
|
||||
\IPS\Db::i()->replace( 'core_login_links', array(
|
||||
Db::i()->replace( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $member->member_id,
|
||||
'token_identifier' => $userId,
|
||||
'token_linked' => 1,
|
||||
'token_access_token' => $accessToken['access_token'],
|
||||
'token_expires' => isset( $accessToken['expires_in'] ) ? ( time() + \intval( $accessToken['expires_in'] ) ) : NULL,
|
||||
'token_refresh_token' => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
|
||||
'token_expires' => isset( $accessToken['expires_in'] ) ? ( time() + intval( $accessToken['expires_in'] ) ) : NULL,
|
||||
'token_refresh_token' => $accessToken['refresh_token'] ?? NULL,
|
||||
'token_scope' => $scope ? json_encode( $scope ) : NULL,
|
||||
) );
|
||||
|
||||
@@ -533,30 +532,30 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
return $member;
|
||||
}
|
||||
catch ( \IPS\Login\Exception $exception )
|
||||
catch ( Exception $exception )
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
if ( $exception->getCode() === Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
{
|
||||
try
|
||||
{
|
||||
$identifier = \IPS\Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
$identifier = Db::i()->select( 'token_identifier', 'core_login_links', array( 'token_login_method=? AND token_member=?', $this->id, $exception->member->member_id ) )->first();
|
||||
|
||||
if( $identifier != $userId )
|
||||
{
|
||||
$exception->setCode( \IPS\Login\Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
$exception->setCode( Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
catch( UnderflowException $e )
|
||||
{
|
||||
\IPS\Db::i()->replace( 'core_login_links', array(
|
||||
Db::i()->replace( 'core_login_links', array(
|
||||
'token_login_method' => $this->id,
|
||||
'token_member' => $exception->member->member_id,
|
||||
'token_identifier' => $userId,
|
||||
'token_linked' => 0,
|
||||
'token_access_token' => $accessToken['access_token'],
|
||||
'token_expires' => isset( $accessToken['expires_in'] ) ? ( time() + \intval( $accessToken['expires_in'] ) ) : NULL,
|
||||
'token_refresh_token' => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
|
||||
'token_expires' => isset( $accessToken['expires_in'] ) ? ( time() + intval( $accessToken['expires_in'] ) ) : NULL,
|
||||
'token_refresh_token' => $accessToken['refresh_token'] ?? NULL,
|
||||
'token_scope' => $scope ? json_encode( $scope ) : NULL,
|
||||
) );
|
||||
}
|
||||
@@ -570,11 +569,11 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Exchange authorization code for access token
|
||||
*
|
||||
* @param string $code Authorization code
|
||||
* @param string $code Authorization code
|
||||
* @return array
|
||||
* @throws \IPS\Login\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _exchangeAuthorizationCodeForAccessToken( $code )
|
||||
protected function _exchangeAuthorizationCodeForAccessToken( string $code ): array
|
||||
{
|
||||
/* Make the request */
|
||||
$data = NULL;
|
||||
@@ -589,24 +588,24 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
if( $this->pkceSupported === TRUE )
|
||||
{
|
||||
$post['code_verifier'] = \IPS\Request::i()->cookie['codeVerifier'] ?: NULL;
|
||||
$post['code_verifier'] = Request::i()->cookie['codeVerifier'] ?: NULL;
|
||||
}
|
||||
|
||||
$data = $this->_authenticatedRequest( $this->tokenEndpoint(), $post );
|
||||
|
||||
$response = $data->decodeJson();
|
||||
\IPS\Request::i()->setCookie('codeVerifier', NULL );
|
||||
Request::i()->setCookie('codeVerifier', NULL );
|
||||
}
|
||||
catch( \RuntimeException $e )
|
||||
catch( RuntimeException $e )
|
||||
{
|
||||
\IPS\Log::log( var_export( $data, true ), 'oauth' );
|
||||
Log::log( var_export( $data, true ), 'oauth' );
|
||||
}
|
||||
|
||||
/* Check for any errors */
|
||||
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) or ( isset( $response['token_type'] ) and mb_strtolower( $response['token_type'] ) !== 'bearer' ) )
|
||||
{
|
||||
\IPS\Log::log( print_r( $response, TRUE ), 'oauth' );
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
Log::log( print_r( $response, TRUE ), 'oauth' );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
@@ -616,10 +615,10 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Get link
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return array
|
||||
* @param Member $member Member
|
||||
* @return array|null
|
||||
*/
|
||||
protected function _link( \IPS\Member $member )
|
||||
protected function _link( Member $member ): ?array
|
||||
{
|
||||
$link = parent::_link( $member );
|
||||
|
||||
@@ -636,18 +635,16 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
{
|
||||
if( !isset( $newAccessToken['error'] ) OR $newAccessToken['error'] != 'invalid_grant' )
|
||||
{
|
||||
\IPS\Log::log( print_r( $newAccessToken, TRUE ), 'oauth' );
|
||||
Log::log( print_r( $newAccessToken, TRUE ), 'oauth' );
|
||||
}
|
||||
|
||||
\IPS\Db::i()->update( 'core_login_links', array( 'token_refresh_token' => NULL ), array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
|
||||
Db::i()->update( 'core_login_links', array( 'token_refresh_token' => NULL ), array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
|
||||
return $link;
|
||||
}
|
||||
|
||||
$update = array();
|
||||
if ( isset( $newAccessToken['access_token'] ) )
|
||||
{
|
||||
$update['token_access_token'] = $newAccessToken['access_token'];
|
||||
}
|
||||
$update = array(
|
||||
'token_access_token' => $newAccessToken['access_token']
|
||||
);
|
||||
if ( isset( $newAccessToken['expires_in'] ) )
|
||||
{
|
||||
$update['token_expires'] = ( time() + $newAccessToken['expires_in'] );
|
||||
@@ -662,11 +659,11 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
$link[ $k ] = $v;
|
||||
$this->_cachedLinks[ $member->member_id ][ $k ] = $v;
|
||||
}
|
||||
\IPS\Db::i()->update( 'core_login_links', $update, array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
|
||||
Db::i()->update( 'core_login_links', $update, array( 'token_login_method=? AND token_member=?', $this->id, $member->member_id ) );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'oauth' );
|
||||
Log::log( $e, 'oauth' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,7 +677,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function grantType();
|
||||
abstract protected function grantType(): string;
|
||||
|
||||
/**
|
||||
* Get scopes to request
|
||||
@@ -688,7 +685,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
return array();
|
||||
}
|
||||
@@ -699,17 +696,18 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* @param string $accessToken Access Token
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function scopesIssued( $accessToken )
|
||||
public function scopesIssued( string $accessToken ): ?array
|
||||
{
|
||||
return $this->scopesToRequest(); // Unless the individual handler overrides this, we'll just assume it's given us what we asked for (which is how the OAuth spec says you're supposed to do it anyway)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Authorized scopes
|
||||
*
|
||||
* @return array|NULL
|
||||
* @param Member $member
|
||||
* @return array|NULL
|
||||
*/
|
||||
public function authorizedScopes( \IPS\Member $member )
|
||||
public function authorizedScopes( Member $member ): ?array
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
@@ -722,35 +720,35 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
abstract protected function authorizationEndpoint( \IPS\Login $login );
|
||||
abstract protected function authorizationEndpoint( Login $login ): Url;
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
abstract protected function tokenEndpoint();
|
||||
abstract protected function tokenEndpoint(): Url;
|
||||
|
||||
/**
|
||||
* Redirection Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function redirectionEndpoint()
|
||||
protected function redirectionEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::internal( 'oauth/callback/', 'none' );
|
||||
return Url::internal( 'oauth/callback/', 'none' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
abstract protected function authenticatedUserId( $accessToken );
|
||||
abstract protected function authenticatedUserId( string $accessToken ): ?string;
|
||||
|
||||
/**
|
||||
* Get authenticated user's username
|
||||
@@ -759,7 +757,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -771,7 +769,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -780,17 +778,17 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* Get user's identifier (may not be a number)
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userId( \IPS\Member $member )
|
||||
public function userId( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedUserId( $link['token_access_token'] );
|
||||
@@ -800,17 +798,17 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedUserName( $link['token_access_token'] );
|
||||
@@ -820,17 +818,17 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* Get user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userEmail( \IPS\Member $member )
|
||||
public function userEmail( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( 'generic_error', Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedEmail( $link['token_access_token'] );
|
||||
@@ -841,10 +839,10 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Show in Account Settings?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member, or NULL for if it should show generally
|
||||
* @param Member|NULL $member The member, or NULL for if it should show generally
|
||||
* @return bool
|
||||
*/
|
||||
public function showInUcp( \IPS\Member $member = NULL )
|
||||
public function showInUcp( Member $member = NULL ): bool
|
||||
{
|
||||
$return = parent::showInUcp( $member );
|
||||
|
||||
@@ -861,7 +859,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSyncOptions()
|
||||
public function hasSyncOptions(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -871,18 +869,18 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*
|
||||
* @return string | NULL
|
||||
*/
|
||||
public function clientSecret()
|
||||
public function clientSecret() : ?string
|
||||
{
|
||||
return isset( $this->settings['client_secret'] ) ? $this->settings['client_secret'] : NULL;
|
||||
return $this->settings['client_secret'] ?? NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Save Add/Edit Form
|
||||
*
|
||||
* @param array $values Values from the form
|
||||
* @return void
|
||||
* @return mixed
|
||||
*/
|
||||
public function saveForm( $values )
|
||||
public function saveForm( array $values ): mixed
|
||||
{
|
||||
/* If we are prompting users we need to disable force syncing */
|
||||
if( isset( $values['login_settings']['real_name'] ) AND $values['login_settings']['real_name'] == 0 )
|
||||
@@ -890,6 +888,6 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
$values['login_settings']['update_name_changes'] = "disabled";
|
||||
}
|
||||
|
||||
parent::saveForm( $values );
|
||||
return parent::saveForm( $values );
|
||||
}
|
||||
}
|
||||
@@ -11,31 +11,39 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open ID Connect
|
||||
*/
|
||||
abstract class _OpenID extends \IPS\Login\Handler\OAuth2
|
||||
abstract class OpenID extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @brief ID Token
|
||||
*/
|
||||
protected $_idToken = array();
|
||||
protected array $_idToken = array();
|
||||
|
||||
/**
|
||||
* Process an Access Token
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param array $accessToken Access Token
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param array $accessToken Access Token
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _processAccessToken( \IPS\Login $login, $accessToken )
|
||||
protected function _processAccessToken( Login $login, array $accessToken ): Member
|
||||
{
|
||||
/* Set up ID token */
|
||||
if( isset( $accessToken['id_token'] ) )
|
||||
@@ -50,16 +58,16 @@ abstract class _OpenID extends \IPS\Login\Handler\OAuth2
|
||||
/* Store the id token */
|
||||
if( $this->_idToken[ $accessToken['access_token'] ] )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_login_links', array( 'token_id_token' => $this->_idToken[ $accessToken['access_token'] ] ), array( 'token_login_method=? and token_member=?', $this->id, $member->member_id ) );
|
||||
Db::i()->update( 'core_login_links', array( 'token_id_token' => $this->_idToken[ $accessToken['access_token'] ] ), array( 'token_login_method=? and token_member=?', $this->id, $member->member_id ) );
|
||||
}
|
||||
|
||||
return $member;
|
||||
}
|
||||
catch ( \Exception $exception )
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
if ( $exception->getCode() === Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_login_links', array( 'token_id_token' => $this->_idToken[ $accessToken['access_token'] ] ), array( 'token_login_method=? and token_member=?', $this->id, $exception->member->member_id ) );
|
||||
Db::i()->update( 'core_login_links', array( 'token_id_token' => $this->_idToken[ $accessToken['access_token'] ] ), array( 'token_login_method=? and token_member=?', $this->id, $exception->member->member_id ) );
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
@@ -69,14 +77,14 @@ abstract class _OpenID extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Retrieve the ID Token
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
*/
|
||||
protected function _getIdToken( $accessToken )
|
||||
protected function _getIdToken( string $accessToken ): string
|
||||
{
|
||||
if( !isset( $this->_idToken[ $accessToken ] ) or !$this->_idToken[ $accessToken ] )
|
||||
{
|
||||
$this->_idToken[ $accessToken ] = \IPS\Db::i()->select( 'token_id_token', 'core_login_links', array( 'token_login_method=? and token_access_token=?', $this->id, $accessToken ) )->first();
|
||||
$this->_idToken[ $accessToken ] = Db::i()->select( 'token_id_token', 'core_login_links', array( 'token_login_method=? and token_access_token=?', $this->id, $accessToken ) )->first();
|
||||
}
|
||||
|
||||
return $this->_idToken[ $accessToken ];
|
||||
|
||||
@@ -11,28 +11,47 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use DomainException;
|
||||
use IPS\File;
|
||||
use IPS\Helpers\Form\Color;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Helpers\Form\Upload;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler\OAuth2;
|
||||
use IPS\Member;
|
||||
use RuntimeException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use const IPS\OAUTH_REQUIRES_HTTPS;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wordpress oAuth Login Handler
|
||||
*/
|
||||
class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
class Wordpress extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @brief Can we have multiple instances of this handler?
|
||||
*/
|
||||
public static $allowMultiple = TRUE;
|
||||
public static bool $allowMultiple = TRUE;
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_wordpress';
|
||||
}
|
||||
@@ -45,7 +64,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$return = array();
|
||||
$return[] = array( 'login_handler_wordpress_settings', 'login_handler_wordpress_oauth_info' );
|
||||
@@ -58,7 +77,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$active = 'accountManagementSettings';
|
||||
}
|
||||
if ( !\is_string( $v ) and !\is_array( $v ) )
|
||||
if ( !is_string( $v ) and !is_array( $v ) )
|
||||
{
|
||||
${$active}[ $k ] = $v;
|
||||
}
|
||||
@@ -66,24 +85,24 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
$endpointValidation = function( $val )
|
||||
{
|
||||
if ( \IPS\OAUTH_REQUIRES_HTTPS and $val and $val instanceof \IPS\Http\Url )
|
||||
if ( OAUTH_REQUIRES_HTTPS and $val and $val instanceof Url )
|
||||
{
|
||||
if ( $val->data[ \IPS\Http\Url::COMPONENT_SCHEME ] !== 'https' )
|
||||
if ( $val->data[ Url::COMPONENT_SCHEME ] !== 'https' )
|
||||
{
|
||||
throw new \DomainException('authorization_endpoint_https');
|
||||
throw new DomainException('authorization_endpoint_https');
|
||||
}
|
||||
if ( $val->data[ \IPS\Http\Url::COMPONENT_FRAGMENT ] )
|
||||
if ( $val->data[ Url::COMPONENT_FRAGMENT ] )
|
||||
{
|
||||
throw new \DomainException('authorization_endpoint_fragment');
|
||||
throw new DomainException('authorization_endpoint_fragment');
|
||||
}
|
||||
}
|
||||
};
|
||||
$return['wordpress_url'] = new \IPS\Helpers\Form\Url( 'wordpress_url', isset( $this->settings['wordpress_url'] ) ? $this->settings['wordpress_url'] : NULL, NULL, array( 'placeholder' => 'https://example.com/' ), $endpointValidation, NULL, NULL, 'wordpress_url' );
|
||||
$return['wordpress_url'] = new \IPS\Helpers\Form\Url( 'wordpress_url', $this->settings['wordpress_url'] ?? NULL, NULL, array( 'placeholder' => 'https://example.com/' ), $endpointValidation, NULL, NULL, 'wordpress_url' );
|
||||
|
||||
$return[] = 'login_handler_oauth_ui';
|
||||
$return['button_color'] = new \IPS\Helpers\Form\Color( 'oauth_custom_button_color', isset( $this->settings['button_color'] ) ? $this->settings['button_color'] : '#23282d', NULL, array(), NULL, NULL, NULL, 'button_color' );
|
||||
$return['button_text'] = new \IPS\Helpers\Form\Translatable( 'oauth_custom_button_text', NULL, NULL, array( 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('oauth_custom_button_text_custom_placeholder'), 'app' => 'core', 'key' => ( $this->id ? "core_custom_oauth_{$this->id}" : NULL ) ), NULL, NULL, NULL, 'button_text' );
|
||||
$return['button_icon'] = new \IPS\Helpers\Form\Upload( 'oauth_custom_button_icon', ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] ) : NULL, FALSE, array( 'storageExtension' => 'core_Login' ), NULL, NULL, NULL, 'button_icon' );
|
||||
$return['button_color'] = new Color( 'oauth_custom_button_color', $this->settings['button_color'] ?? '#23282d', NULL, array(), NULL, NULL, NULL, 'button_color' );
|
||||
$return['button_text'] = new Translatable( 'oauth_custom_button_text', NULL, NULL, array( 'placeholder' => Member::loggedIn()->language()->addToStack('oauth_custom_button_text_custom_placeholder'), 'app' => 'core', 'key' => ( $this->id ? "core_custom_oauth_{$this->id}" : NULL ) ), NULL, NULL, NULL, 'button_text' );
|
||||
$return['button_icon'] = new Upload( 'oauth_custom_button_icon', ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] ) : NULL, FALSE, array( 'storageExtension' => 'core_Login' ), NULL, NULL, NULL, 'button_icon' );
|
||||
|
||||
$return[] = 'account_management_settings';
|
||||
foreach ( $accountManagementSettings as $k => $v )
|
||||
@@ -100,7 +119,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
public function acpFormSave( array &$values ): array
|
||||
{
|
||||
$return = parent::acpFormSave( $values );
|
||||
$return['button_icon'] = (string) $return['button_icon'];
|
||||
@@ -114,7 +133,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* @param array $values Values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFormValues( $values )
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
$parent = parent::formatFormValues( $values );
|
||||
|
||||
@@ -124,7 +143,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$this->save();
|
||||
}
|
||||
\IPS\Lang::saveCustom( 'core', "core_custom_oauth_{$this->id}", $values['oauth_custom_button_text'] );
|
||||
Lang::saveCustom( 'core', "core_custom_oauth_{$this->id}", $values['oauth_custom_button_text'] );
|
||||
unset( $values['button_text'] );
|
||||
}
|
||||
|
||||
@@ -136,7 +155,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
public function buttonColor(): string
|
||||
{
|
||||
return $this->settings['button_color'];
|
||||
}
|
||||
@@ -146,28 +165,28 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
public function buttonIcon(): string
|
||||
{
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] ) : 'wordpress';
|
||||
return 'wordpress';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in information about logins with this method
|
||||
* Returns NULL for methods where it is not necessary to indicate the method, e..g Standard
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
public function logoForDeviceInformation(): Url|string|null
|
||||
{
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? \IPS\File::get( 'core_Login', $this->settings['button_icon'] )->url : NULL;
|
||||
return ( isset( $this->settings['button_icon'] ) and $this->settings['button_icon'] ) ? File::get( 'core_Login', $this->settings['button_icon'] )->url : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo to display in user cp sidebar
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url|string|null
|
||||
*/
|
||||
public function logoForUcp()
|
||||
public function logoForUcp(): Url|string|null
|
||||
{
|
||||
return $this->logoForDeviceInformation() ?: 'wordpress';
|
||||
}
|
||||
@@ -177,7 +196,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
public function buttonText(): string
|
||||
{
|
||||
return "core_custom_oauth_{$this->id}";
|
||||
}
|
||||
@@ -187,7 +206,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
protected function _authenticationType(): string
|
||||
{
|
||||
return static::AUTHENTICATE_POST; // Just because it's possible their server isn't configured to accept HTTP Authorization whereas we know this will always work
|
||||
}
|
||||
@@ -197,7 +216,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
protected function grantType(): string
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
@@ -208,7 +227,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
protected function scopesToRequest( array $additional=NULL ): array
|
||||
{
|
||||
return array( "profile" );
|
||||
}
|
||||
@@ -216,31 +235,31 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
* @param Login $login The login object
|
||||
* @return Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
protected function authorizationEndpoint( Login $login ): Url
|
||||
{
|
||||
return \IPS\Http\Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/authorize" );
|
||||
return Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/authorize" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
* @return Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
protected function tokenEndpoint(): Url
|
||||
{
|
||||
return \IPS\Http\Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/token" );
|
||||
return Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/token" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's identifier (may not be a number)
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|null
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
protected function authenticatedUserId( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->_userData( $accessToken )[ 'username' ] ) )
|
||||
{
|
||||
@@ -256,7 +275,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
protected function authenticatedUserName( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->_userData( $accessToken )[ 'display_name' ] ) )
|
||||
{
|
||||
@@ -272,7 +291,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
protected function authenticatedEmail( string $accessToken ): ?string
|
||||
{
|
||||
if ( isset( $this->_userData( $accessToken )[ 'email' ] ) )
|
||||
{
|
||||
@@ -285,17 +304,17 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile photo
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @param Member $member Member
|
||||
* @return Url|NULL
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
public function userProfilePhoto( Member $member ): ?Url
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
if ( isset( $this->_userData( $link['token_access_token'] )[ 'avatar' ] ) )
|
||||
@@ -304,7 +323,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
try
|
||||
{
|
||||
return \IPS\Http\Url::external( $url );
|
||||
return Url::external( $url );
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
@@ -315,17 +334,17 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param Member $member Member
|
||||
* @return string|NULL
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws DomainException General error where it is safe to show a message to the user
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
public function userProfileName( Member $member ): ?string
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
throw new Exception( "", Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->authenticatedUserName( $link['token_access_token'] );
|
||||
@@ -334,11 +353,11 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Syncing Options
|
||||
*
|
||||
* @param \IPS\Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param Member $member The member we're asking for (can be used to not show certain options iof the user didn't grant those scopes)
|
||||
* @param bool $defaultOnly If TRUE, only returns which options should be enabled by default for a new account
|
||||
* @return array
|
||||
*/
|
||||
public function syncOptions( \IPS\Member $member, $defaultOnly = FALSE )
|
||||
public function syncOptions( Member $member, bool $defaultOnly=FALSE ): array
|
||||
{
|
||||
$return = array();
|
||||
|
||||
@@ -358,20 +377,20 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* @brief Cached user data
|
||||
*/
|
||||
protected $_cachedUserData = array();
|
||||
protected array $_cachedUserData = array();
|
||||
|
||||
/**
|
||||
* Get user data
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
* @throws Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws RuntimeException Unexpected error from service
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
protected function _userData( string $accessToken ): array
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$data = \IPS\Http\Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/resource" )
|
||||
$data = Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/resource" )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
@@ -380,9 +399,9 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
->decodeJson();
|
||||
|
||||
/* As with the Custom handler - if we did not get expected data, try using the query string method. */
|
||||
if ( !\count( $data ) )
|
||||
if ( !count( $data ) )
|
||||
{
|
||||
$data = \IPS\Http\Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/resource" )
|
||||
$data = Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-json/moserver/resource" )
|
||||
->setQueryString( 'access_token', $accessToken )
|
||||
->request()
|
||||
->get()
|
||||
@@ -397,10 +416,10 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
/**
|
||||
* Forgot Password URL
|
||||
*
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forgotPasswordUrl()
|
||||
public function forgotPasswordUrl(): ?Url
|
||||
{
|
||||
return \IPS\Http\Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-login.php?action=lostpassword" );
|
||||
return Url::external( rtrim( $this->settings['wordpress_url'], "/" ) . "/wp-login.php?action=lostpassword" );
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,28 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Login\Handler;
|
||||
use IPS\Member;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Request;
|
||||
use function count;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Internal Database Login Handler
|
||||
*/
|
||||
class _Standard extends \IPS\Login\Handler
|
||||
class Standard extends Handler
|
||||
{
|
||||
use UsernamePasswordHandler;
|
||||
|
||||
@@ -29,83 +41,66 @@ class _Standard extends \IPS\Login\Handler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
public static function getTitle(): string
|
||||
{
|
||||
return 'login_handler_Internal';
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @return array List of settings to save - settings will be stored to core_login_methods.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm(): array
|
||||
{
|
||||
$id = $this->id ?: 'new';
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
|
||||
public function authenticateUsernamePassword( Login $login, string $usernameOrEmail, object $password ): Member
|
||||
{
|
||||
$type = 'username_or_email';
|
||||
switch ( $this->authType() )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* Make sure we have the username or email */
|
||||
if( !$usernameOrEmail )
|
||||
{
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::NO_ACCOUNT );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE ), Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
/* Get member(s) */
|
||||
$where = array();
|
||||
$params = array();
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
$where[] = 'name=?';
|
||||
$params[] = $usernameOrEmail;
|
||||
|
||||
if ( $usernameOrEmail !== \IPS\Request::legacyEscape( $usernameOrEmail ) )
|
||||
{
|
||||
$where[] = 'name=?';
|
||||
$params[] = \IPS\Request::legacyEscape( $usernameOrEmail );
|
||||
}
|
||||
}
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
|
||||
$where[] = 'email=?';
|
||||
$params[] = $usernameOrEmail;
|
||||
|
||||
if ( $usernameOrEmail !== Request::legacyEscape( $usernameOrEmail ) )
|
||||
{
|
||||
$where[] = 'email=?';
|
||||
$params[] = $usernameOrEmail;
|
||||
|
||||
if ( $usernameOrEmail !== \IPS\Request::legacyEscape( $usernameOrEmail ) )
|
||||
{
|
||||
$where[] = 'email=?';
|
||||
$params[] = \IPS\Request::legacyEscape( $usernameOrEmail );
|
||||
}
|
||||
$params[] = Request::legacyEscape( $usernameOrEmail );
|
||||
}
|
||||
$members = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array_merge( array( implode( ' OR ', $where ) ), $params ) ), 'IPS\Member' );
|
||||
|
||||
$members = new ActiveRecordIterator( Db::i()->select( '*', 'core_members', array_merge( array( implode( ' OR ', $where ) ), $params ) ), 'IPS\Member' );
|
||||
|
||||
/* If we didn't match any, throw an exception */
|
||||
if ( !\count( $members ) )
|
||||
if ( !count( $members ) )
|
||||
{
|
||||
$member = NULL;
|
||||
|
||||
if ( $this->authType() & \IPS\Login::AUTH_TYPE_EMAIL )
|
||||
{
|
||||
$member = new \IPS\Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
}
|
||||
$member = new Member;
|
||||
$member->email = $usernameOrEmail;
|
||||
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::NO_ACCOUNT, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_no_account', FALSE ), Exception::NO_ACCOUNT, NULL, $member );
|
||||
}
|
||||
|
||||
/* Check the password for each possible account */
|
||||
@@ -126,17 +121,17 @@ class _Standard extends \IPS\Login\Handler
|
||||
}
|
||||
|
||||
/* Still here? Throw a password incorrect exception */
|
||||
throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE, array( 'pluralize' => array( $this->authType() ) ) ), \IPS\Login\Exception::BAD_PASSWORD, NULL, $member );
|
||||
throw new Exception( Member::loggedIn()->language()->addToStack( 'login_err_bad_password', FALSE ), Exception::BAD_PASSWORD, NULL, $member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticatePasswordForMember( \IPS\Member $member, $password )
|
||||
public function authenticatePasswordForMember( Member $member, object $password ): bool
|
||||
{
|
||||
if ( password_verify( $password, $member->members_pass_hash ) === TRUE )
|
||||
{
|
||||
@@ -149,23 +144,25 @@ class _Standard extends \IPS\Login\Handler
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Can this handler process a login for a member?
|
||||
* Can this handler process a login for a member?
|
||||
*
|
||||
* @return bool
|
||||
* @param Member $member
|
||||
* @return bool
|
||||
*/
|
||||
public function canProcess( \IPS\Member $member )
|
||||
public function canProcess( Member $member ): bool
|
||||
{
|
||||
return (bool) $member->members_pass_hash;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Can this handler process a password change for a member?
|
||||
* Can this handler process a password change for a member?
|
||||
*
|
||||
* @return bool
|
||||
* @param Member $member
|
||||
* @return bool
|
||||
*/
|
||||
public function canChangePassword( \IPS\Member $member )
|
||||
public function canChangePassword( Member $member ): bool
|
||||
{
|
||||
/* If it's forced, then yes. */
|
||||
if ( $member->members_bitoptions['password_reset_forced'] AND !$member->members_pass_hash )
|
||||
@@ -181,7 +178,7 @@ class _Standard extends \IPS\Login\Handler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canSyncPassword()
|
||||
public function canSyncPassword(): bool
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -189,11 +186,11 @@ class _Standard extends \IPS\Login\Handler
|
||||
/**
|
||||
* Change Password
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $newPassword New Password wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param object|string $newPassword New Password wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return void
|
||||
*/
|
||||
public function changePassword( \IPS\Member $member, $newPassword )
|
||||
public function changePassword( Member $member, object|string $newPassword ) : void
|
||||
{
|
||||
$member->setLocalPassword( $newPassword );
|
||||
$member->save();
|
||||
@@ -202,11 +199,11 @@ class _Standard extends \IPS\Login\Handler
|
||||
/**
|
||||
* Force Password Reset URL
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param \IPS\Http\Url|NULL $ref Referrer
|
||||
* @return \IPS\Http\Url|NULL
|
||||
* @param Member $member The member
|
||||
* @param Url|NULL $ref Referrer
|
||||
* @return Url|NULL
|
||||
*/
|
||||
public function forcePasswordResetUrl( \IPS\Member $member, ?\IPS\Http\Url $ref = NULL ): ?\IPS\Http\Url
|
||||
public function forcePasswordResetUrl( Member $member, ?Url $ref = NULL ): ?Url
|
||||
{
|
||||
return $member->passwordResetForced( $ref );
|
||||
}
|
||||
@@ -214,10 +211,10 @@ class _Standard extends \IPS\Login\Handler
|
||||
/**
|
||||
* Show in Account Settings?
|
||||
*
|
||||
* @param \IPS\Member|NULL $member The member, or NULL for if it should show generally
|
||||
* @param Member|NULL $member The member, or NULL for if it should show generally
|
||||
* @return bool
|
||||
*/
|
||||
public function showInUcp( \IPS\Member $member = NULL )
|
||||
public function showInUcp( Member $member = NULL ): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,16 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Login;
|
||||
use IPS\Login\Exception;
|
||||
use IPS\Member;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -28,19 +35,9 @@ trait UsernamePasswordHandler
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function type()
|
||||
public function type(): int
|
||||
{
|
||||
return \IPS\Login::TYPE_USERNAME_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get auth type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function authType()
|
||||
{
|
||||
return $this->settings['auth_types'];
|
||||
return Login::TYPE_USERNAME_PASSWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,35 +48,35 @@ trait UsernamePasswordHandler
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
public function acpForm(): array
|
||||
{
|
||||
$id = $this->id ?: 'new';
|
||||
return array(
|
||||
'auth_types' => new \IPS\Helpers\Form\Select( 'login_auth_types', isset( $this->settings['auth_types'] ) ? $this->settings['auth_types'] : ( \IPS\Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
\IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
\IPS\Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
\IPS\Login::AUTH_TYPE_USERNAME => 'username',
|
||||
), 'toggles' => array( \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL => array( 'form_' . $id . '_login_auth_types_warning' ), \IPS\Login::AUTH_TYPE_USERNAME => array( 'form_' . $id . '_login_auth_types_warning' ) ) ) )
|
||||
'auth_types' => new Select( 'login_auth_types', $this->settings['auth_types'] ?? ( Login::AUTH_TYPE_EMAIL ), TRUE, array( 'options' => array(
|
||||
Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => 'username_or_email',
|
||||
Login::AUTH_TYPE_EMAIL => 'email_address',
|
||||
Login::AUTH_TYPE_USERNAME => 'username',
|
||||
), 'toggles' => array( Login::AUTH_TYPE_USERNAME + Login::AUTH_TYPE_EMAIL => array( 'form_' . $id . '_login_auth_types_warning' ), Login::AUTH_TYPE_USERNAME => array( 'form_' . $id . '_login_auth_types_warning' ) ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return \IPS\Member
|
||||
* @throws \IPS\Login\Exception
|
||||
* @param Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return Member
|
||||
* @throws Exception
|
||||
*/
|
||||
abstract public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password );
|
||||
abstract public function authenticateUsernamePassword( Login $login, string $usernameOrEmail, object $password ): Member;
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @param Member $member The member
|
||||
* @param object $password The plaintext password provided by the user, wrapped in an object that can be cast to a string so it doesn't show in any logs
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function authenticatePasswordForMember( \IPS\Member $member, $password );
|
||||
abstract public function authenticatePasswordForMember( Member $member, object $password ): bool;
|
||||
}
|
||||
Reference in new issue
Block a user