Files
IPB/system/Login/Google.php
T
2025-12-19 04:57:54 -08:00

229 lines
8.2 KiB
PHP

<?php
/**
* @brief Google Login Handler
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @since 20 Mar 2013
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Login;
/* 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;
}
/**
* Google Login Handler
*/
class _Google extends LoginAbstract
{
/**
* @brief Icon
*/
public static $icon = 'google-plus';
/**
* Get Form
*
* @param string $url The URL for the login page
* @param bool $ucp Is UCP? (as opposed to login form)
* @return string
*/
public function loginForm( $url, $ucp=FALSE )
{
return \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->google( (string) $this->_googleSignInUrl( ( $ucp ? 'ucp' : \IPS\Dispatcher::i()->controllerLocation ), $ucp ) );
}
/**
* Get sign in URL
*
* @param string $base Controls where the user is taken back to
* @param bool $forcePrompt Force prompt? Will mean user is shown the Google site even if previously authenticated. Only to be used if a new refresh token is needed.
* @return \IPS\Http\Url
*/
protected function _googleSignInUrl( $base, $forcePrompt )
{
$params = array(
'response_type' => 'code',
'client_id' => $this->settings['client_id'],
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' ),
'scope' => 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'access_type' => 'offline',
'state' => $base . '-' . \IPS\Session::i()->csrfKey
);
if ( $forcePrompt )
{
$params['approval_prompt'] = 'force';
}
return \IPS\Http\Url::external( "https://accounts.google.com/o/oauth2/auth" )->setQueryString( $params );
}
/**
* Authenticate
*
* @param string $url The URL for the login page
* @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object
* @return \IPS\Member
* @throws \IPS\Login\Exception
*/
public function authenticate( $url, $member=NULL )
{
try
{
/* CSRF Check */
if ( \IPS\Request::i()->state !== \IPS\Session::i()->csrfKey )
{
throw new \IPS\Login\Exception( 'CSRF_FAIL', \IPS\Login\Exception::INTERNAL_ERROR );
}
/* Get access token so we can get user data */
$response = \IPS\Http\Url::external( "https://accounts.google.com/o/oauth2/token" )->request()->post( array(
'code' => \IPS\Request::i()->code,
'client_id' => $this->settings['client_id'],
'client_secret' => $this->settings['client_secret'],
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' ),
'grant_type' => 'authorization_code',
) )->decodeJson();
if ( isset( $response['error'] ) or !isset( $response['access_token'] ) )
{
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
}
/* Get user data */
$userData = \IPS\Http\Url::external( "https://www.googleapis.com/plus/v1/people/me?access_token={$response['access_token']}" )->request()->get()->decodeJson('id');
$email = $userData = \IPS\Http\Url::external( "https://www.googleapis.com/oauth2/v1/userinfo?access_token={$response['access_token']}" )->request()->get()->decodeJson('email');
/* Set member properties */
$memberProperties = array( 'google_id' => $userData['id'] );
if ( isset( $response['refresh_token'] ) )
{
$memberProperties['google_token'] = $response['refresh_token'];
}
/* Find or create member */
$member = $this->createOrUpdateAccount( $member ?: \IPS\Member::load( $userData['id'], 'google_id' ), $memberProperties, $this->settings['real_name'] ? $userData['name'] : NULL, $userData['email'], $response['access_token'], array( 'photo' => TRUE, 'cover' => TRUE, 'status' => '' ) );
/* We need a refresh token so we can get their user data again later.
This is only provided if they actually clicked through the Google auth screen
and not if they were authenticated automatically. So if we don't have one,
send them back, forcing the login screen, so we get one */
if ( !$member->google_token )
{
\IPS\Output::i()->redirect( $this->_googleSignInUrl( \IPS\Request::i()->base, TRUE ) );
}
/* Return */
return $member;
}
catch ( \IPS\Http\Request\Exception $e )
{
throw new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::INTERNAL_ERROR );
}
}
/**
* Link Account
*
* @param \IPS\Member $member The member
* @param mixed $details Details as they were passed to the exception thrown in authenticate()
* @return void
*/
public static function link( \IPS\Member $member, $details )
{
$userData = \IPS\Http\Url::external( "https://www.googleapis.com/plus/v1/people/me?access_token={$details}" )->request()->get()->decodeJson('id');
$member->google_id = $userData['id'];
$member->google_token = $details;
$member->save();
}
/**
* ACP Settings Form
*
* @param string $url URL to redirect user to after successful submission
* @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field
* @code
return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... );
* @endcode
*/
public function acpForm()
{
\IPS\Output::i()->sidebar['actions'] = array(
'help' => array(
'title' => 'help',
'icon' => 'question-circle',
'link' => \IPS\Http\Url::ips( 'docs/login_google' ),
'target' => '_blank',
'class' => ''
),
);
return array(
'client_id' => new \IPS\Helpers\Form\Text( 'login_google_id', ( isset( $this->settings['client_id'] ) ) ? $this->settings['client_id'] : '', TRUE ),
'client_secret' => new \IPS\Helpers\Form\Text( 'login_google_secret', ( isset( $this->settings['client_secret'] ) ) ? $this->settings['client_secret'] : '', TRUE ),
'real_name' => new \IPS\Helpers\Form\YesNo( 'login_real_name', ( isset( $this->settings['real_name'] ) ) ? $this->settings['real_name'] : FALSE, TRUE )
);
}
/**
* Test Settings
*
* @return bool
* @throws \IPS\Http\Request\Exception
* @throws \UnexpectedValueException If response code is not 302
*/
public function testSettings()
{
try
{
$response = \IPS\Http\Url::external( "https://accounts.google.com/o/oauth2/auth" )->setQueryString( array(
'response_type' => 'code',
'client_id' => $this->settings['client_id'],
'redirect_uri' => (string) \IPS\Http\Url::internal( 'applications/core/interface/google/auth.php', 'none' ),
'scope' => 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'state' => 'admin-' . \IPS\Session::i()->csrfKey
) )->request()->get();
if ( $response->httpResponseCode != 200 )
{
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Google') ) ) ) );
}
}
catch ( \IPS\Http\Request\Exception $e )
{
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack('login_3p_bad', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('login_handler_Google') ) ) ) );
}
return TRUE;
}
/**
* Can a member sign in with this login handler?
* Used to ensure when a user disassociates a social login that they have some other way of logging in
*
* @param \IPS\Member $member The member
* @return bool
*/
public function canProcess( \IPS\Member $member )
{
return ( $member->google_id and $member->google_token );
}
/**
* Can a member change their email/password with this login handler?
*
* @param string $type 'email' or 'password'
* @param \IPS\Member $member The member
* @return bool
*/
public function canChange( $type, \IPS\Member $member )
{
return FALSE;
}
}