Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
@@ -0,0 +1,465 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Sign In With Apple Login Handler
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 2 Jun 2020
|
||||
*/
|
||||
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign In With Apple Login Handler
|
||||
*/
|
||||
class _Apple extends \IPS\Login\Handler\OAuth2\OpenID
|
||||
{
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTitle()
|
||||
{
|
||||
return 'login_handler_Apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* Should client credentials be sent as an "Authorisation" header, or as POST data?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _authenticationType()
|
||||
{
|
||||
return static::AUTHENTICATE_POST;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Settings Form
|
||||
*
|
||||
* @return array List of settings to save - settings will be stored to core_login_methods.login_settings DB field
|
||||
* @code
|
||||
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
|
||||
* @endcode
|
||||
*/
|
||||
public function acpForm()
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->words['oauth_client_id'] = \IPS\Member::loggedIn()->language()->addToStack('login_apple_services_id');
|
||||
|
||||
$return = array();
|
||||
$return[] = array( 'login_handler_apple_settings', 'login_handler_Apple_info' );
|
||||
|
||||
$accountManagementSettings = array();
|
||||
$active = 'return';
|
||||
|
||||
foreach ( parent::acpForm() as $k => $v )
|
||||
{
|
||||
if ( $v === 'account_management_settings' )
|
||||
{
|
||||
$active = 'accountManagementSettings';
|
||||
}
|
||||
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[] = 'account_management_settings';
|
||||
foreach ( $accountManagementSettings as $k => $v )
|
||||
{
|
||||
$return[ $k ] = $v;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Handler Settings
|
||||
*
|
||||
* @param array $values Values from form
|
||||
* @return array
|
||||
*/
|
||||
public function acpFormSave( &$values )
|
||||
{
|
||||
$return = parent::acpFormSave( $values );
|
||||
$return['apple_key'] = (string) $return['apple_key'];
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the button color
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonColor()
|
||||
{
|
||||
return '#000000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the button icon
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonIcon()
|
||||
{
|
||||
return 'apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonText()
|
||||
{
|
||||
return 'login_apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function buttonClass()
|
||||
{
|
||||
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
|
||||
*/
|
||||
public function logoForDeviceInformation()
|
||||
{
|
||||
return \IPS\Theme::i()->resource( 'logos/login/Apple.png', 'core', 'interface' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function grantType()
|
||||
{
|
||||
return 'authorization_code';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scopes to request
|
||||
*
|
||||
* @param array|NULL $additional Any additional scopes to request
|
||||
* @return array
|
||||
*/
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
{
|
||||
$return = array( 'name email' );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Endpoint
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
{
|
||||
$return = \IPS\Http\Url::external( 'https://appleid.apple.com/auth/authorize' )->setQueryString( 'response_mode', 'form_post' );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Endpoint
|
||||
*
|
||||
* @return \IPS\Http\Url
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
{
|
||||
return \IPS\Http\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
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
{
|
||||
$claims = $this->getClaimsfromIdToken( $this->_getIdToken( $accessToken ) );
|
||||
|
||||
return ( isset( $claims['sub'] ) ) ? $claims['sub'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's username
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
{
|
||||
$name = NULL;
|
||||
|
||||
if( isset( $_SESSION['oauth_user'] ) )
|
||||
{
|
||||
$session = json_decode( $_SESSION['oauth_user'], true );
|
||||
|
||||
if( isset( $session['name'] ) )
|
||||
{
|
||||
$name = implode( " ", $session['name'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user's email address
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string|NULL
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
{
|
||||
$claims = $this->getClaimsfromIdToken( $this->_getIdToken( $accessToken ) );
|
||||
|
||||
return ( isset( $claims['email'] ) ) ? $claims['email'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client Secret
|
||||
*
|
||||
* @return string | NULL
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public function clientSecret()
|
||||
{
|
||||
$key = NULL;
|
||||
|
||||
if ( isset( $this->settings['apple_key'] ) and $this->settings['apple_key'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$key = \IPS\File::get( 'core_Login', $this->settings['apple_key'] )->contents();
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if( !$key )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$kid = $this->settings['apple_key_id'];
|
||||
$iss = $this->settings['apple_team_id'];
|
||||
$sub = $this->settings['client_id'];
|
||||
|
||||
$header = array(
|
||||
'alg' => 'ES256',
|
||||
'kid' => $kid
|
||||
);
|
||||
|
||||
$data = array(
|
||||
'iss' => $iss,
|
||||
'iat' => time(),
|
||||
'exp' => time() + 3600,
|
||||
'aud' => 'https://appleid.apple.com',
|
||||
'sub' => $sub
|
||||
);
|
||||
|
||||
$pKey = openssl_pkey_get_private( $key );
|
||||
|
||||
if ( !$pKey )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'login_apple_invalid_key', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$payload = $this->baseURL64encode( json_encode( $header ) ) . '.' . $this->baseURL64encode( json_encode( $data ) );
|
||||
|
||||
$signature = '';
|
||||
$success = openssl_sign( $payload, $signature, $pKey, OPENSSL_ALGO_SHA256 );
|
||||
if ( !$success )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$rawSignature = $this->fromDER( $signature, 64 );
|
||||
|
||||
return $payload. '.' . $this->baseURL64encode( $rawSignature );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 )
|
||||
{
|
||||
$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 ) ) )
|
||||
{
|
||||
$return[] = 'email';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an ID Token
|
||||
*
|
||||
* @param array $iDToken ID Token
|
||||
* @return array
|
||||
*/
|
||||
protected function getClaimsfromIdToken ( $iDToken )
|
||||
{
|
||||
$claims = explode( '.', $iDToken )[1];
|
||||
$claims = json_decode( base64_decode( $claims ), true );
|
||||
|
||||
return $claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Key From DER
|
||||
*
|
||||
* @param string $der
|
||||
* @param int $partLength
|
||||
*
|
||||
* @return string
|
||||
* @throws \IPS\Login\Exception
|
||||
*/
|
||||
public static function fromDER( string $der, int $partLength )
|
||||
{
|
||||
$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 );
|
||||
}
|
||||
|
||||
if ( '81' === mb_substr( $hex, 2, 2, '8bit' ) )
|
||||
{
|
||||
$hex = mb_substr( $hex, 6, null, '8bit' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$hex = mb_substr( $hex, 4, null, '8bit' );
|
||||
}
|
||||
|
||||
if ( '02' !== mb_substr( $hex, 0, 2, '8bit' ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$Rl = hexdec( mb_substr( $hex, 2, 2, '8bit' ) );
|
||||
$R = self::retrievePositiveInteger( mb_substr( $hex, 4, $Rl * 2, '8bit' ) );
|
||||
$R = str_pad( $R, $partLength, '0', STR_PAD_LEFT );
|
||||
$hex = mb_substr( $hex, 4 + $Rl * 2, null, '8bit' );
|
||||
|
||||
if ( '02' !== mb_substr( $hex, 0, 2, '8bit' ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$Sl = hexdec( mb_substr( $hex, 2, 2, '8bit' ) );
|
||||
$S = self::retrievePositiveInteger( mb_substr( $hex, 4, $Sl * 2, '8bit' ) );
|
||||
$S = str_pad( $S, $partLength, '0', STR_PAD_LEFT );
|
||||
|
||||
return pack( 'H*', $R . $S );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare Integer
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected static function preparePositiveInteger( string $data )
|
||||
{
|
||||
if ( mb_substr( $data, 0, 2, '8bit') > '7f' )
|
||||
{
|
||||
return '00' . $data;
|
||||
}
|
||||
while ( '00' === mb_substr( $data, 0, 2, '8bit' ) && mb_substr( $data, 2, 2, '8bit' ) <= '7f' )
|
||||
{
|
||||
$data = mb_substr( $data, 2, null, '8bit' );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Integer
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected static function retrievePositiveInteger( string $data )
|
||||
{
|
||||
while ( '00' === mb_substr( $data, 0, 2, '8bit' ) && mb_substr( $data, 2, 2, '8bit' ) > '7f' )
|
||||
{
|
||||
$data = mb_substr( $data, 2, null, '8bit' );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode text in baseurl64 format
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected function baseURL64encode( $data )
|
||||
{
|
||||
$encoded = strtr( base64_encode( $data ), '+/', '-_' );
|
||||
|
||||
return rtrim( $encoded, '=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if ( isset( $this->settings['apple_key'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Login', $this->settings['apple_key'] )->delete();
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
|
||||
parent::delete();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -73,7 +73,7 @@ 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;
|
||||
}
|
||||
@@ -344,7 +344,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( isset( $this->settings['photo_field'] ) and $this->settings['photo_field'] )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -370,7 +370,7 @@ class _Custom extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/**
|
||||
* OAuth2 Exception
|
||||
*/
|
||||
class _Exception extends \Exception
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -159,12 +159,12 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
$return[] = 'user_posts';
|
||||
}
|
||||
|
||||
$additionalPermitted = array( 'manage_pages', 'publish_pages', 'publish_to_groups' );
|
||||
$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;
|
||||
}
|
||||
@@ -327,39 +327,12 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $error['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function userCoverPhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
|
||||
if ( isset( $userData['cover'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['cover']['source'] );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +347,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userStatuses( \IPS\Member $member, \IPS\DateTime $since = NULL )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -418,7 +391,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
$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';
|
||||
}
|
||||
@@ -429,9 +402,8 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
}
|
||||
|
||||
$return[] = 'photo';
|
||||
$return[] = 'cover';
|
||||
|
||||
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 ) ) ) )
|
||||
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';
|
||||
}
|
||||
@@ -457,7 +429,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = $this->_authorizedRequest( 'me', $accessToken, array(
|
||||
'fields' => 'email,id,name,picture,cover',
|
||||
'fields' => 'email,id,name,picture',
|
||||
'appsecret_proof' => hash_hmac( 'sha256', $accessToken, $this->settings['client_secret'] )
|
||||
), 'get' );
|
||||
|
||||
@@ -513,7 +485,7 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function postToFacebook( \IPS\Member $member, $content, \IPS\Http\Url $url = NULL )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -641,9 +613,9 @@ class _Facebook extends \IPS\Login\Handler\OAuth2
|
||||
if ( \is_numeric( \IPS\Settings::i()->promote_facebook_auth ) )
|
||||
{
|
||||
/* standard handler */
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
if ( !( $link = $this->_link( $member ) ) or ( $link['token_expires'] and $link['token_expires'] < time() ) )
|
||||
{
|
||||
return $pages;
|
||||
return array();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -59,10 +59,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
)
|
||||
), NULL, NULL, NULL, 'login_real_name' )
|
||||
),
|
||||
parent::acpForm(),
|
||||
array(
|
||||
'allow_status_import' => new \IPS\Helpers\Form\YesNo( 'login_generic_allow_status_import', ( isset( $this->settings['allow_status_import'] ) ) ? $this->settings['allow_status_import'] : FALSE, FALSE )
|
||||
)
|
||||
parent::acpForm()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,12 +136,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
'profile',
|
||||
'email',
|
||||
);
|
||||
|
||||
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] )
|
||||
{
|
||||
$return[] = 'https://www.googleapis.com/auth/plus.me';
|
||||
}
|
||||
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -231,7 +223,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
{
|
||||
return $this->_userData( $accessToken )['id'];
|
||||
return $this->_userData( $accessToken )['sub'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +237,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
return $this->_userData( $accessToken )['displayName'];
|
||||
return $this->_userData( $accessToken )['name'];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -259,11 +251,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
{
|
||||
foreach ( $this->_userData( $accessToken )['emails'] as $email )
|
||||
{
|
||||
return $email['value'];
|
||||
}
|
||||
return NULL;
|
||||
return $this->_userData( $accessToken )['email'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,15 +266,15 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
if ( isset( $userData['image'] ) and !$userData['image']['isDefault'] )
|
||||
if ( isset( $userData['picture'] ) and $userData['picture'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['image']['url'] )->setQueryString( 'sz', NULL );
|
||||
return \IPS\Http\Url::external( $userData['picture'] )->setQueryString( 'sz', NULL );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -303,87 +291,14 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['displayName'];
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function userCoverPhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
|
||||
if ( isset( $userData['cover'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['cover']['coverPhoto']['url'] )->setQueryString( 'sz', NULL );
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's statuses since a particular date
|
||||
*
|
||||
* @param \IPS\Member $member Member
|
||||
* @param \IPS\DateTime|NULL $since Date/Time to get statuses since then, or NULL to get the latest one
|
||||
* @return array
|
||||
* @throws \IPS\Login\Exception The token is invalid and the user needs to reauthenticate
|
||||
* @throws \DomainException General error where it is safe to show a message to the user
|
||||
* @throws \RuntimeException Unexpected error from service
|
||||
*/
|
||||
public function userStatuses( \IPS\Member $member, \IPS\DateTime $since = NULL )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$response = \IPS\Http\Url::external( "https://www.googleapis.com/plus/v1/people/me/activities/public" )
|
||||
->setQueryString( $since ? array() : array( 'maxResults' => 1 ) )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$link['token_access_token']}"
|
||||
) )
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
$return = array();
|
||||
foreach ( $response['items'] as $statusData )
|
||||
{
|
||||
if ( $since and strtotime( $statusData['published'] ) < $since->getTimestamp() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ( isset( $statusData['object']['content'] ) )
|
||||
{
|
||||
$status = \IPS\core\Statuses\Status::createItem( $member, $member->ip_address, new \IPS\DateTime( $statusData['published'] ) );
|
||||
$status->content = $this->_parseStatusText( $member, nl2br( $statusData['object']['content'], FALSE ) );
|
||||
|
||||
$return[] = $status;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get link to user's remote profile
|
||||
* May return NULL if server doesn't support this
|
||||
@@ -397,7 +312,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userLink( $identifier, $username )
|
||||
{
|
||||
return \IPS\Http\Url::external( "https://plus.google.com/" . $identifier );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -418,7 +333,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';
|
||||
}
|
||||
@@ -429,13 +344,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
}
|
||||
|
||||
$return[] = 'photo';
|
||||
$return[] = 'cover';
|
||||
|
||||
if ( \IPS\Settings::i()->profile_comments and isset( $this->settings['allow_status_import'] ) and $this->settings['allow_status_import'] and in_array( 'https://www.googleapis.com/auth/plus.me', $authorizedScopes ) )
|
||||
{
|
||||
$return[] = 'status';
|
||||
}
|
||||
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -455,7 +364,7 @@ class _Google extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://www.googleapis.com/plus/v1/people/me" )
|
||||
$response = \IPS\Http\Url::external( "https://www.googleapis.com/oauth2/v3/userinfo" )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -77,7 +77,7 @@ 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;
|
||||
}
|
||||
@@ -265,7 +265,12 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authenticatedUserId( $accessToken )
|
||||
{
|
||||
return $this->_userData( $accessToken )['id'];
|
||||
$userData = $this->_userData( $accessToken );
|
||||
if ( isset( $userData['id'] ) )
|
||||
{
|
||||
return $userData['id'];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,7 +282,12 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authenticatedUserName( $accessToken )
|
||||
{
|
||||
return $this->_userData( $accessToken )['name'];
|
||||
$userData = $this->_userData( $accessToken );
|
||||
if ( isset( $userData['name'] ) )
|
||||
{
|
||||
return $userData['name'];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,13 +319,13 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
if ( !isset( $userData['photoUrlIsDefault'] ) or !$userData['photoUrlIsDefault'] )
|
||||
if ( ( !isset( $userData['photoUrlIsDefault'] ) or !$userData['photoUrlIsDefault'] ) AND isset( $userData['photoUrl'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $userData['photoUrl'] );
|
||||
}
|
||||
@@ -334,12 +344,18 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
if( isset( $userData['name'] ) )
|
||||
{
|
||||
return $userData['name'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,7 +370,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userCoverPhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -422,6 +438,7 @@ class _Invision extends \IPS\Login\Handler\OAuth2
|
||||
* @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
|
||||
*/
|
||||
protected function _userData( $accessToken )
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -32,6 +32,9 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
return 'login_handler_Linkedin';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable AdminCP logins by default
|
||||
*/
|
||||
protected static $enableAcpLoginByDefault = FALSE;
|
||||
|
||||
/**
|
||||
@@ -140,7 +143,7 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
{
|
||||
return array(
|
||||
'r_basicprofile',
|
||||
'r_liteprofile',
|
||||
'r_emailaddress',
|
||||
);
|
||||
}
|
||||
@@ -202,10 +205,15 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
return (string) $this->_userData( $accessToken )['formattedName'];
|
||||
return $this->_getProfileName( $accessToken );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cached email address to make sure we don't request more than once
|
||||
*/
|
||||
protected $_cachedEmail = NULL;
|
||||
|
||||
/**
|
||||
* Get authenticated user's email address
|
||||
@@ -216,7 +224,22 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
{
|
||||
return $this->_userData( $accessToken )['emailAddress'];
|
||||
if ( $this->_cachedEmail === NULL )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))" )
|
||||
->request()
|
||||
->setHeaders( array( 'Authorization' => "Bearer {$accessToken}" ) )
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
if ( isset( $response['errorCode'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$this->_cachedEmail = $response['elements'][0]['handle~']['emailAddress'];
|
||||
}
|
||||
return $this->_cachedEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,18 +254,29 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] );
|
||||
|
||||
if ( isset( $userData['pictureUrls'] ) AND isset( $userData['pictureUrls']['_total'] ) AND $userData['pictureUrls']['_total'] )
|
||||
if( !isset( $this->_userData( $link['token_access_token'] )['profilePicture'] ) )
|
||||
{
|
||||
foreach ( $this->_userData( $link['token_access_token'] )['pictureUrls']['values'] as $picture )
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$userData = $this->_userData( $link['token_access_token'] )['profilePicture']['displayImage~']['elements'];
|
||||
|
||||
foreach( array_reverse( $userData ) as $_userData )
|
||||
{
|
||||
if ( isset( $_userData['identifiers'] ) )
|
||||
{
|
||||
return \IPS\Http\Url::external( $picture );
|
||||
foreach( $_userData['identifiers'] as $identifier )
|
||||
{
|
||||
if( isset( $identifier['identifier'] ) AND $identifier['identifier'] )
|
||||
{
|
||||
return \IPS\Http\Url::external( $identifier['identifier'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
@@ -260,28 +294,44 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['formattedName'];
|
||||
|
||||
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
|
||||
* @return string
|
||||
*/
|
||||
protected function _getProfileName( $accessToken )
|
||||
{
|
||||
$preferredLocale = $this->_userData( $accessToken )['firstName']['preferredLocale']['language'] . '_' . $this->_userData( $accessToken )['firstName']['preferredLocale']['country'];
|
||||
|
||||
return ( $this->_userData( $accessToken )['firstName']['localized'][ $preferredLocale ] . ' ' . $this->_userData( $accessToken )['lastName']['localized'][ $preferredLocale ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get link to user's remote profile
|
||||
* May return NULL if server doesn't support this
|
||||
*
|
||||
* @param string $identifier The ID Nnumber/string from remote service
|
||||
* @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
|
||||
* @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 )
|
||||
{
|
||||
return \IPS\Http\Url::external( "https://www.linkedin.com/in/" )->setQueryString( 'id', $identifier );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -327,13 +377,13 @@ class _LinkedIn extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://api.linkedin.com/v1/people/~:(id,formattedName,emailAddress,picture-urls::(original),member-url-resources)" )
|
||||
->setQueryString( array( 'format' => 'json' ) )
|
||||
$response = \IPS\Http\Url::external( "https://api.linkedin.com/v2/me" )
|
||||
->setQueryString( 'projection', '(id,firstName,lastName,vanityName,profilePicture(displayImage~:playableStreams))' )
|
||||
->request()
|
||||
->setHeaders( array( 'Authorization' => "Bearer {$accessToken}" ) )
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
|
||||
if ( isset( $response['errorCode'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -131,9 +131,9 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
protected function scopesToRequest( $additional=NULL )
|
||||
{
|
||||
return array(
|
||||
'wl.signin',
|
||||
'wl.emails',
|
||||
'wl.offline_access'
|
||||
'openid',
|
||||
'User.Read',
|
||||
'offline_access'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authorizationEndpoint( \IPS\Login $login )
|
||||
{
|
||||
return \IPS\Http\Url::external('https://login.live.com/oauth20_authorize.srf');
|
||||
return \IPS\Http\Url::external('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +155,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function tokenEndpoint()
|
||||
{
|
||||
return \IPS\Http\Url::external('https://login.live.com/oauth20_token.srf');
|
||||
return \IPS\Http\Url::external('https://login.microsoftonline.com/common/oauth2/v2.0/token');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +194,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( isset( $this->settings['real_name'] ) and $this->settings['real_name'] )
|
||||
{
|
||||
return $this->_userData( $accessToken )['name'];
|
||||
return $this->_userData( $accessToken )['displayName'];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -208,34 +208,9 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
protected function authenticatedEmail( $accessToken )
|
||||
{
|
||||
return $this->_userData( $accessToken )['emails']['preferred'];
|
||||
return $this->_userData( $accessToken )['userPrincipalName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( NULL, \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
}
|
||||
|
||||
$url = \IPS\Http\Url::external( "https://apis.live.net/v5.0/me/picture" )->setQueryString( 'access_token', $link['token_access_token'] );
|
||||
if ( $url->request()->head()->httpResponseCode == 404 )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get user's profile name
|
||||
* May return NULL if server doesn't support this
|
||||
@@ -248,12 +223,12 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
return $this->_userData( $link['token_access_token'] )['name'];
|
||||
return $this->_userData( $link['token_access_token'] )['displayName'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,9 +251,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
$return[] = 'name';
|
||||
}
|
||||
|
||||
$return[] = 'photo';
|
||||
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -298,14 +271,14 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
if ( !isset( $this->_cachedUserData[ $accessToken ] ) )
|
||||
{
|
||||
$response = \IPS\Http\Url::external( "https://apis.live.net/v5.0/me" )
|
||||
$response = \IPS\Http\Url::external( "https://graph.microsoft.com/v1.0/me" )
|
||||
->request()
|
||||
->setHeaders( array(
|
||||
'Authorization' => "Bearer {$accessToken}"
|
||||
) )
|
||||
->get()
|
||||
->decodeJson();
|
||||
|
||||
|
||||
if ( isset( $response['error'] ) )
|
||||
{
|
||||
throw new \IPS\Login\Exception( $response['error']['message'], \IPS\Login\Exception::INTERNAL_ERROR );
|
||||
@@ -313,6 +286,7 @@ class _Microsoft extends \IPS\Login\Handler\OAuth2
|
||||
|
||||
$this->_cachedUserData[ $accessToken ] = $response;
|
||||
}
|
||||
|
||||
return $this->_cachedUserData[ $accessToken ];
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace IPS\Login\Handler;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -29,6 +29,11 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
* @brief Any additional scopes to authenticate with
|
||||
*/
|
||||
public $additionalScopes = NULL;
|
||||
|
||||
/**
|
||||
* @brief Does this handler support PKCE?
|
||||
*/
|
||||
public $pkceSupported = TRUE;
|
||||
|
||||
/**
|
||||
* Get type
|
||||
@@ -70,16 +75,30 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
'loggedin' => 'login_handler_show_in_ucp_loggedin',
|
||||
),
|
||||
) );
|
||||
|
||||
$nameChangesDisabled = array();
|
||||
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 ) );
|
||||
}
|
||||
$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(
|
||||
'force' => 'login_update_changes_yes',
|
||||
'force' => 'login_update_changes_yes_name',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
) ), NULL, NULL, NULL, 'login_update_name_changes_inc_optional' );
|
||||
), 'disabled' => $nameChangesDisabled ), NULL, NULL, NULL, 'login_update_name_changes_inc_optional' );
|
||||
|
||||
$emailChangesDisabled = array();
|
||||
if ( $forceEmailHandler = static::handlerHasForceSync( 'email', $this ) )
|
||||
{
|
||||
$emailChangesDisabled[] = 'force';
|
||||
\IPS\Member::loggedIn()->language()->words['login_update_changes_yes_email_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'login_update_changes_yes_disabled', FALSE, array( 'sprintf' => $forceEmailHandler->_title ) );
|
||||
}
|
||||
$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(
|
||||
'force' => 'login_update_changes_yes',
|
||||
'force' => 'login_update_changes_yes_email',
|
||||
'optional' => 'login_update_changes_optional',
|
||||
'disabled' => 'login_update_changes_no',
|
||||
) ), NULL, NULL, NULL, 'login_update_email_changes_inc_optional' );
|
||||
), 'disabled' => $emailChangesDisabled ), NULL, NULL, NULL, 'login_update_email_changes_inc_optional' );
|
||||
|
||||
return array_merge( $return, parent::acpForm() );
|
||||
}
|
||||
@@ -159,12 +178,21 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/* Otherwise send them to the Authorization Endpoint */
|
||||
else
|
||||
{
|
||||
$target = $this->authorizationEndpoint( $login )->setQueryString( array(
|
||||
$data = array(
|
||||
'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,
|
||||
) );
|
||||
);
|
||||
|
||||
if ( $this->grantType() === 'authorization_code' AND $this->pkceSupported === TRUE )
|
||||
{
|
||||
$_SESSION['codeVerifier'] = \IPS\Login::generateRandomString( 128 );
|
||||
$data['code_challenge'] = rtrim( strtr( base64_encode( pack( 'H*', hash( 'sha256', $_SESSION['codeVerifier'] ) ) ), '+/', '-_' ), '=' );
|
||||
$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 ) )
|
||||
{
|
||||
@@ -184,16 +212,21 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*
|
||||
* @param \IPS\Login $login The login object
|
||||
* @param string $usernameOrEmail The username or email address provided by the user
|
||||
* @param string $password The plaintext password 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
|
||||
*/
|
||||
public function authenticateUsernamePassword( \IPS\Login $login, $usernameOrEmail, $password )
|
||||
{
|
||||
if( !$usernameOrEmail )
|
||||
{
|
||||
throw new \IPS\Login\Exception( 'login_bad_username_or_password', \IPS\Login\Exception::NO_ACCOUNT );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'grant_type' => 'password',
|
||||
'grant_type' => 'password',
|
||||
'username' => $usernameOrEmail,
|
||||
'password' => $password,
|
||||
'password' => (string) $password,
|
||||
);
|
||||
if ( $scopes = $this->scopesToRequest() )
|
||||
{
|
||||
@@ -229,8 +262,8 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param \IPS\Member $member The member
|
||||
* @param string $password The plaintext password provided by the user
|
||||
* @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
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticatePasswordForMember( \IPS\Member $member, $password )
|
||||
@@ -242,7 +275,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
'grant_type' => 'password',
|
||||
'username' => $member->name,
|
||||
'password' => $password,
|
||||
'password' => (string) $password,
|
||||
) )->decodeJson();
|
||||
if ( isset( $response['access_token'] ) )
|
||||
{
|
||||
@@ -259,7 +292,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
$response = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
'grant_type' => 'password',
|
||||
'username' => $member->email,
|
||||
'password' => $password,
|
||||
'password' => (string) $password,
|
||||
) )->decodeJson();
|
||||
if ( isset( $response['access_token'] ) )
|
||||
{
|
||||
@@ -299,12 +332,12 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
if ( $this->_authenticationType() === static::AUTHENTICATE_HEADER )
|
||||
{
|
||||
$request = $request->login( $this->settings['client_id'], $this->settings['client_secret'] );
|
||||
$request = $request->login( $this->settings['client_id'], $this->clientSecret() );
|
||||
}
|
||||
else
|
||||
{
|
||||
$data['client_id'] = $this->settings['client_id'];
|
||||
$data['client_secret'] = $this->settings['client_secret'];
|
||||
$data['client_secret'] = $this->clientSecret();
|
||||
}
|
||||
|
||||
return $request->post( $data );
|
||||
@@ -398,13 +431,23 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
throw new \UnderflowException;
|
||||
}
|
||||
|
||||
/* Otherwise, update our token... */
|
||||
\IPS\Db::i()->update( 'core_login_links', array(
|
||||
/* 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_refresh_token' => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
|
||||
'token_scope' => $scope ? json_encode( $scope ) : NULL,
|
||||
), array( 'token_login_method=? AND token_member=?', $this->id, $oauthAccess['token_member'] ) );
|
||||
'token_expires' => ( isset( $accessToken['expires_in'] ) ) ? ( time() + \intval( $accessToken['expires_in'] ) ) : NULL
|
||||
);
|
||||
|
||||
if( isset( $accessToken['refresh_token'] ) )
|
||||
{
|
||||
$update['token_refresh_token'] = $accessToken['refresh_token'];
|
||||
}
|
||||
|
||||
if( $scope )
|
||||
{
|
||||
$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'] ) );
|
||||
|
||||
/* ... and return the member object */
|
||||
return $member;
|
||||
@@ -445,7 +488,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
'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_expires' => isset( $accessToken['expires_in'] ) ? ( time() + \intval( $accessToken['expires_in'] ) ) : NULL,
|
||||
'token_refresh_token' => isset( $accessToken['refresh_token'] ) ? $accessToken['refresh_token'] : NULL,
|
||||
'token_scope' => $scope ? json_encode( $scope ) : NULL,
|
||||
) );
|
||||
@@ -476,16 +519,29 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT )
|
||||
{
|
||||
\IPS\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_scope' => $scope ? json_encode( $scope ) : NULL,
|
||||
) );
|
||||
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();
|
||||
|
||||
if( $identifier != $userId )
|
||||
{
|
||||
$exception->setCode( \IPS\Login\Exception::LOCAL_ACCOUNT_ALREADY_MERGED );
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
\IPS\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_scope' => $scope ? json_encode( $scope ) : NULL,
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
@@ -507,12 +563,21 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
$response = array();
|
||||
try
|
||||
{
|
||||
$data = $this->_authenticatedRequest( $this->tokenEndpoint(), array(
|
||||
$post = array(
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
'redirect_uri' => (string) $this->redirectionEndpoint(),
|
||||
) );
|
||||
);
|
||||
|
||||
if( $this->pkceSupported === TRUE )
|
||||
{
|
||||
$post['code_verifier'] = $_SESSION['codeVerifier'];
|
||||
}
|
||||
|
||||
$data = $this->_authenticatedRequest( $this->tokenEndpoint(), $post );
|
||||
|
||||
$response = $data->decodeJson();
|
||||
unset( $_SESSION['codeVerifier'] );
|
||||
}
|
||||
catch( \RuntimeException $e )
|
||||
{
|
||||
@@ -551,7 +616,11 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
|
||||
if ( isset( $newAccessToken['error'] ) or !isset( $newAccessToken['access_token'] ) or ( isset( $newAccessToken['token_type'] ) and mb_strtolower( $newAccessToken['token_type'] ) !== 'bearer' ) )
|
||||
{
|
||||
\IPS\Log::log( print_r( $newAccessToken, TRUE ), 'oauth' );
|
||||
if( !isset( $newAccessToken['error'] ) OR $newAccessToken['error'] != 'invalid_grant' )
|
||||
{
|
||||
\IPS\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 ) );
|
||||
return $link;
|
||||
}
|
||||
@@ -701,7 +770,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*/
|
||||
public function userId( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -721,7 +790,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -741,7 +810,7 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
*/
|
||||
public function userEmail( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -765,4 +834,25 @@ abstract class _OAuth2 extends \IPS\Login\Handler
|
||||
}
|
||||
return parent::showInUcp( $member );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Has any sync options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSyncOptions()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client Secret
|
||||
*
|
||||
* @return string | NULL
|
||||
*/
|
||||
public function clientSecret()
|
||||
{
|
||||
return isset( $this->settings['client_secret'] ) ? $this->settings['client_secret'] : NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Open ID Connect Handler
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 9 Jun 2020
|
||||
*/
|
||||
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open ID Connect
|
||||
*/
|
||||
abstract class _OpenID extends \IPS\Login\Handler\OAuth2
|
||||
{
|
||||
/**
|
||||
* @brief ID Token
|
||||
*/
|
||||
protected $_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
|
||||
*/
|
||||
protected function _processAccessToken( \IPS\Login $login, $accessToken )
|
||||
{
|
||||
/* Set up ID token */
|
||||
if( isset( $accessToken['id_token'] ) )
|
||||
{
|
||||
$this->_idToken[ $accessToken['access_token'] ] = $accessToken['id_token'];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$member = parent::_processAccessToken( $login, $accessToken );
|
||||
|
||||
/* 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 ) );
|
||||
}
|
||||
|
||||
return $member;
|
||||
}
|
||||
catch ( \Exception $exception )
|
||||
{
|
||||
if ( $exception->getCode() === \IPS\Login\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 ) );
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID Token
|
||||
*
|
||||
* @param string $accessToken Access Token
|
||||
* @return string
|
||||
*/
|
||||
protected function _getIdToken( $accessToken )
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
return $this->_idToken[ $accessToken ];
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Login\Handler\OAuth2;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
@@ -58,7 +58,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;
|
||||
}
|
||||
@@ -283,35 +283,20 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfilePhoto( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
|
||||
if ( isset( $this->_userData( $link['token_access_token'] )[ 'avatar' ] ) )
|
||||
{
|
||||
{
|
||||
$url = preg_replace( '/^.*src=[\'"](.+?)[\'"].*$/i', '$1', $this->_userData( $link['token_access_token'] )[ 'avatar' ] );
|
||||
if ( preg_match( '/^https?:\/\/(?:secure\.|www\.)?gravatar\.com\/avatar\/(.{32})/', $url, $matches ) )
|
||||
|
||||
try
|
||||
{
|
||||
$url = \IPS\Http\Url::external( "https://secure.gravatar.com/avatar/" . $matches[1] )->setQueryString( 'd', '404' );
|
||||
try
|
||||
{
|
||||
$response = $url->request()->head();
|
||||
if ( $response->httpResponseCode == 200 )
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
return \IPS\Http\Url::external( $url );
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
return \IPS\Http\Url::external( $url );
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -328,7 +313,7 @@ class _Wordpress extends \IPS\Login\Handler\OAuth2
|
||||
*/
|
||||
public function userProfileName( \IPS\Member $member )
|
||||
{
|
||||
if ( !( $link = $this->_link( $member ) ) )
|
||||
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 );
|
||||
}
|
||||
@@ -385,7 +370,7 @@ 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-content/plugins/miniorange-oauth-20-server/web/moserver/resource" )
|
||||
->setQueryString( 'access_token', $accessToken )
|
||||
|
||||
Whitespace-only changes.
Reference in new issue
Block a user