Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user