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