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,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" );
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user