Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+112 -94
View File
@@ -7,7 +7,26 @@
* @package Invision Community
* @since 29 Apr 2017
*/
\define('REPORT_EXCEPTIONS', TRUE);
use IPS\Api\OAuthClient;
use IPS\Db;
use IPS\Dispatcher;
use IPS\Dispatcher\External;
use IPS\Http\Url;
use IPS\Login;
use IPS\Login\Handler\OAuth2\InitException;
use IPS\Member;
use IPS\Member\Device;
use IPS\Output;
use IPS\Request;
use IPS\Session;
use IPS\Session\Front;
use IPS\Settings;
use IPS\Theme;
use const IPS\DEBUG_OAUTH_REDIRECTS;
use const IPS\OAUTH_REQUIRES_HTTPS;
define('REPORT_EXCEPTIONS', TRUE);
require '../../init.php';
class oAuthServerAuthorizationRequest
@@ -69,43 +88,43 @@ class oAuthServerAuthorizationRequest
/* Get the client */
try
{
$obj->client = \IPS\Api\OAuthClient::load( $clientId );
$obj->client = OAuthClient::load( $clientId );
if ( !$obj->client->enabled )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
catch ( \OutOfRangeException $e )
catch (OutOfRangeException $e )
{
throw new \IPS\Login\Handler\OAuth2\InitException('oauth_err_invalid_client');
throw new InitException('oauth_err_invalid_client');
}
/* Set the Redirect URI */
$allowedRedirectUris = json_decode( $obj->client->redirect_uris );
if( \defined('\IPS\DEBUG_OAUTH_REDIRECTS') )
if( defined('\IPS\DEBUG_OAUTH_REDIRECTS') )
{
$allowedRedirectUris = array_merge( $allowedRedirectUris, \IPS\DEBUG_OAUTH_REDIRECTS );
$allowedRedirectUris = array_merge( $allowedRedirectUris, DEBUG_OAUTH_REDIRECTS );
}
if ( $redirectUri )
{
if ( !\in_array( $redirectUri, $allowedRedirectUris ) )
if ( !in_array( $redirectUri, $allowedRedirectUris ) )
{
throw new \IPS\Login\Handler\OAuth2\InitException('oauth_err_invalid_redirect_uri');
throw new InitException('oauth_err_invalid_redirect_uri');
}
else
{
$obj->redirectUri = \IPS\Http\Url::external( $redirectUri );
$obj->redirectUri = Url::external( $redirectUri );
}
}
elseif ( \count( $allowedRedirectUris ) === 1 )
elseif ( count( $allowedRedirectUris ) === 1 )
{
$obj->redirectUri = \IPS\Http\Url::external( array_shift( $allowedRedirectUris ) );
$obj->redirectUri = Url::external( array_shift( $allowedRedirectUris ) );
}
else
{
throw new \IPS\Login\Handler\OAuth2\InitException('oauth_err_invalid_redirect_uri');
throw new InitException('oauth_err_invalid_redirect_uri');
}
$obj->providedRedirectUri = $redirectUri;
@@ -149,14 +168,14 @@ class oAuthServerAuthorizationRequest
{
if ( $responseType === 'code' )
{
if ( !\in_array( 'authorization_code', explode( ',', $this->client->grant_types ) ) )
if ( !in_array( 'authorization_code', explode( ',', $this->client->grant_types ) ) )
{
throw new \IPS\Login\Handler\OAuth2\Exception('unsupported_response_type');
}
}
elseif ( $responseType === 'token' )
{
if ( !\in_array( 'implicit', explode( ',', $this->client->grant_types ) ) )
if ( !in_array( 'implicit', explode( ',', $this->client->grant_types ) ) )
{
throw new \IPS\Login\Handler\OAuth2\Exception('unsupported_response_type');
}
@@ -192,24 +211,24 @@ class oAuthServerAuthorizationRequest
/**
* Get URL to redirect the user back to the client after successful authorization
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @param array $scopes The authorized scopes
* @return void
*/
public function authorized( \IPS\Member $member, $scopes )
public function authorized( Member $member, $scopes )
{
$scopes = $this->client->choose_scopes ? $scopes : $this->scope;
$device = \IPS\Member\Device::loadOrCreate( $member );
$device = Device::loadOrCreate( $member );
if ( $this->responseType === 'code' )
{
do
{
$authorizationCode = \IPS\Login::generateRandomString( 64 );
$authorizationCode = Login::generateRandomString( 64 );
}
while ( \IPS\Db::i()->select( 'COUNT(*)', 'core_oauth_server_authorization_codes', array( 'client_id=? AND code=?', $this->client->client_id, $authorizationCode ) )->first() );
while ( Db::i()->select( 'COUNT(*)', 'core_oauth_server_authorization_codes', array( 'client_id=? AND code=?', $this->client->client_id, $authorizationCode ) )->first() );
\IPS\Db::i()->insert( 'core_oauth_server_authorization_codes', array(
Db::i()->insert( 'core_oauth_server_authorization_codes', array(
'client_id' => $this->client->client_id,
'redirect_uri' => $this->providedRedirectUri ?: NULL,
'member_id' => $member->member_id,
@@ -270,27 +289,27 @@ class oAuthServerAuthorizationRequest
public function promptRequired( $requestedPromptType )
{
/* If we're not logged in, we definitely do, unless we cancelled */
if ( !\IPS\Member::loggedIn()->member_id and ( !isset( \IPS\Request::i()->allow ) or \IPS\Request::i()->allow ) )
if ( !Member::loggedIn()->member_id and ( !isset( Request::i()->allow ) or Request::i()->allow ) )
{
return TRUE;
}
/* If we're banned or validating, we'll show those screens instead */
if ( \IPS\Member::loggedIn()->isBanned() or \IPS\Member::loggedIn()->members_bitoptions['validating'] )
if ( Member::loggedIn()->isBanned() or Member::loggedIn()->members_bitoptions['validating'] )
{
return TRUE;
}
/* If our account is incomplete (e.g. no name or no email), show that screen instead */
if( \IPS\Member::loggedIn()->member_id and !( \IPS\Member::loggedIn()->real_name and \IPS\Member::loggedIn()->email ) )
if( Member::loggedIn()->member_id and !( Member::loggedIn()->real_name and Member::loggedIn()->email ) )
{
return TRUE;
}
/* Have we gone through it already? */
if ( isset( \IPS\Request::i()->allow ) and \IPS\Login::compareHashes( (string) \IPS\Session::i()->csrfKey, (string) \IPS\Request::i()->csrfKey ) )
if ( isset( Request::i()->allow ) and Login::compareHashes( (string) Session::i()->csrfKey, (string) Request::i()->csrfKey ) )
{
if ( !\IPS\Request::i()->allow )
if ( !Request::i()->allow )
{
throw new \IPS\Login\Handler\OAuth2\Exception('access_denied');
}
@@ -298,7 +317,7 @@ class oAuthServerAuthorizationRequest
}
/* Does the client require it? */
if ( !\in_array( $this->client->prompt, array( 'none', 'automatic' ) ) )
if ( !in_array( $this->client->prompt, array( 'none', 'automatic' ) ) )
{
return TRUE;
}
@@ -316,10 +335,10 @@ class oAuthServerAuthorizationRequest
}
/* Do we already have an access token with these scopes? */
$accessToken = $this->client->getAccessToken( \IPS\Member::loggedIn(), $this->scope );
$accessToken = $this->client->getAccessToken( Member::loggedIn(), $this->scope );
if ( $accessToken )
{
\IPS\Request::i()->grantedScope = $accessToken['scope'] ? array_combine( json_decode( $accessToken['scope'], TRUE ), array_fill( 0, \count( json_decode( $accessToken['scope'], TRUE ) ), TRUE ) ) : array();
Request::i()->grantedScope = $accessToken['scope'] ? array_combine( json_decode( $accessToken['scope'], TRUE ), array_fill( 0, count( json_decode( $accessToken['scope'], TRUE ) ), TRUE ) ) : array();
return FALSE;
}
@@ -337,26 +356,26 @@ class oAuthServerAuthorizationRequest
public function prompt( $requestedPromptType, $loggedIn )
{
/* If we're banned or validating, we'll show those screens instead */
if ( \IPS\Member::loggedIn()->member_id and \IPS\Member::loggedIn()->isBanned() )
if ( Member::loggedIn()->member_id and Member::loggedIn()->isBanned() )
{
\IPS\Output::i()->showBanned();
Output::i()->showBanned();
exit;
}
elseif ( \IPS\Member::loggedIn()->members_bitoptions['validating'] )
elseif ( Member::loggedIn()->members_bitoptions['validating'] )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=validating', 'front', 'register' ) );
Output::i()->redirect( Url::internal( 'app=core&module=system&controller=register&do=validating', 'front', 'register' ) );
}
/* We mustn't have the redirect_uri in the URL when displaying the page as this needs to be handled securely (it will
probably include a client-issued CSRF key) and if we use it in the URL, any third party scripts that may be being
used on the community (tracking or advertisements, for example) will have access to it - this also makes the URI
a bit cleaner */
if ( isset( \IPS\Request::i()->client_id ) )
if ( isset( Request::i()->client_id ) )
{
$key = md5( uniqid() );
\IPS\Request::i()->setCookie( 'oauth_authorize', $key );
Request::i()->setCookie( 'oauth_authorize', $key );
\IPS\Db::i()->insert( 'core_oauth_authorize_prompts', array(
Db::i()->insert( 'core_oauth_authorize_prompts', array(
'session_id' => $key,
'client_id' => $this->client->client_id,
'response_type' => $this->responseType,
@@ -365,24 +384,24 @@ class oAuthServerAuthorizationRequest
'state' => $this->state,
'timestamp' => time(),
'logged_in' => FALSE,
'prompt' => \in_array( $requestedPromptType, array( 'login', 'reauthorize' ) ) ? $requestedPromptType : NULL,
'prompt' => in_array( $requestedPromptType, array( 'login', 'reauthorize' ) ) ? $requestedPromptType : NULL,
'code_challenge' => $this->codeChallenge,
'code_challenge_method' => $this->codeChallengeMethod
), TRUE );
$url = \IPS\Http\Url::internal( 'oauth/authorize/', 'interface' );
if ( isset( \IPS\Request::i()->_processLogin ) ) // This is if they clicked a social sign in button on the registration form throwing them back to here
$url = Url::internal( 'oauth/authorize/', 'interface' );
if ( isset( Request::i()->_processLogin ) ) // This is if they clicked a social sign in button on the registration form throwing them back to here
{
$url = $url->setQueryString( array(
'_processLogin' => \IPS\Request::i()->_processLogin,
'csrfKey' => \IPS\Request::i()->csrfKey,
'_processLogin' => Request::i()->_processLogin,
'csrfKey' => Request::i()->csrfKey,
) );
}
\IPS\Output::i()->redirect( $url );
Output::i()->redirect( $url );
}
/* Construct the URL for this page */
$url = \IPS\Http\Url::internal( 'oauth/authorize/', 'interface' );
$url = Url::internal( 'oauth/authorize/', 'interface' );
/* Get the scope definitions */
$scopes = array();
@@ -393,9 +412,9 @@ class oAuthServerAuthorizationRequest
}
/* Do we need them to login? */
if ( !\IPS\Member::loggedIn()->member_id or ( ( $this->client->prompt === 'login' or $requestedPromptType === 'login' ) and !$loggedIn ) )
if ( !Member::loggedIn()->member_id or ( ( $this->client->prompt === 'login' or $requestedPromptType === 'login' ) and !$loggedIn ) )
{
$login = new \IPS\Login( $url );
$login = new Login( $url );
$member = NULL;
$error = NULL;
@@ -403,16 +422,16 @@ class oAuthServerAuthorizationRequest
{
if ( $success = $login->authenticate() )
{
\IPS\Db::i()->update( 'core_oauth_authorize_prompts', array( 'logged_in' => TRUE, 'prompt' => NULL ), array( 'session_id=?', \IPS\Request::i()->cookie['oauth_authorize'] ) );
Db::i()->update( 'core_oauth_authorize_prompts', array( 'logged_in' => TRUE, 'prompt' => NULL ), array( 'session_id=?', Request::i()->cookie['oauth_authorize'] ) );
if ( $success->mfa() )
{
$_SESSION['processing2FA'] = array( 'memberId' => $success->member->member_id, 'anonymous' => $success->anonymous, 'remember' => $success->rememberMe, 'destination' => (string) $url, 'handler' => $success->handler->id );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
Output::i()->redirect( Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
}
$success->process();
\IPS\Output::i()->redirect( $url );
Output::i()->redirect( $url );
}
}
catch ( \IPS\Login\Exception $e )
@@ -423,7 +442,7 @@ class oAuthServerAuthorizationRequest
$e->handler = $e->handler->id;
$_SESSION['linkAccounts'] = json_encode( $e );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=login&do=link', 'front', 'login' )->setQueryString( 'ref', base64_encode( $url ) ) );
Output::i()->redirect( Url::internal( 'app=core&module=system&controller=login&do=link', 'front', 'login' )->setQueryString( 'ref', base64_encode( $url ) ) );
}
$error = $e->getMessage();
@@ -431,37 +450,36 @@ class oAuthServerAuthorizationRequest
if ( $member === NULL )
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->oauthLogin( $url, $this->client, $scopes, $login, $error );
\IPS\Dispatcher::i()->finish();
Output::i()->output = Theme::i()->getTemplate( 'login', 'core', 'global' )->oauthLogin( $url, $this->client, $scopes, $login, $error );
Dispatcher::i()->finish();
}
}
/* If we're logged in but the account is incomplete (e.g. social registration with hitherto unset name/email), redirect to complete registration first */
elseif ( \IPS\Member::loggedIn()->member_id and !( \IPS\Member::loggedIn()->real_name and \IPS\Member::loggedIn()->email ) )
elseif ( Member::loggedIn()->member_id and !( Member::loggedIn()->real_name and Member::loggedIn()->email ) )
{
$url = \IPS\Http\Url::internal( 'oauth/authorize/', 'interface' );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=complete', 'front', 'register' )->addRef( $url )->setQueryString( 'oauth', 1 ) );
$url = Url::internal( 'oauth/authorize/', 'interface' );
Output::i()->redirect( Url::internal( 'app=core&module=system&controller=register&do=complete', 'front', 'register' )->addRef( $url )->setQueryString( 'oauth', 1 ) );
}
/* Still here? Show an authorization screen */
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->oauthAuthorize( $url, $this->client, $scopes );
\IPS\Dispatcher::i()->finish();
Output::i()->output = Theme::i()->getTemplate( 'login', 'core', 'global' )->oauthAuthorize( $url, $this->client, $scopes );
Dispatcher::i()->finish();
}
}
/* Init */
\IPS\Session\Front::i();
\IPS\Dispatcher\External::i();
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimalNoHome';
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'oauth_authorize', FALSE, array( 'sprintf' => array( \IPS\Settings::i()->board_name ) ) );
\IPS\Output::i()->httpHeaders['X-Frame-Options'] = 'DENY';
\IPS\Output::i()->httpHeaders['Cross-Origin-Opener-Policy'] = 'same-origin';
\IPS\Output::setCacheTime( false );
Front::i();
External::i();
Output::i()->bodyClasses[] = 'ipsLayout_minimal';
Output::i()->bodyClasses[] = 'ipsLayout_minimalNoHome';
Output::i()->title = Member::loggedIn()->language()->addToStack( 'oauth_authorize', FALSE, array( 'sprintf' => array( Settings::i()->board_name ) ) );
Output::i()->httpHeaders['X-Frame-Options'] = 'DENY';
Output::i()->pageCaching = FALSE;
/* Check we are not banned */
if ( \IPS\Request::i()->ipAddressIsBanned() or ( \IPS\Member::loggedIn()->member_id and \IPS\Member::loggedIn()->isBanned() ) )
if ( Request::i()->ipAddressIsBanned() or ( Member::loggedIn()->member_id and Member::loggedIn()->isBanned() ) )
{
\IPS\Output::i()->showBanned();
Output::i()->showBanned();
}
/* Handle the OAuth request */
@@ -469,11 +487,11 @@ try
{
/* Get our params */
$loggedIn = FALSE;
if ( !isset( \IPS\Request::i()->client_id ) and isset( \IPS\Request::i()->cookie['oauth_authorize'] ) )
if ( !isset( Request::i()->client_id ) and isset( Request::i()->cookie['oauth_authorize'] ) )
{
try
{
$row = \IPS\Db::i()->select( '*', 'core_oauth_authorize_prompts', array( 'session_id=?', \IPS\Request::i()->cookie['oauth_authorize'] ) )->first();
$row = Db::i()->select( '*', 'core_oauth_authorize_prompts', array( 'session_id=?', Request::i()->cookie['oauth_authorize'] ) )->first();
$clientId = $row['client_id'];
$responseType = $row['response_type'];
$redirectUri = $row['redirect_uri'];
@@ -484,35 +502,35 @@ try
$codeChallenge = $row['code_challenge'];
$codeChallengeMethod = $row['code_challenge_method'];
if ( isset( \IPS\Request::i()->prompt ) and \in_array( \IPS\Request::i()->prompt, array( 'login', 'reauthorize' ) ) )
if ( isset( Request::i()->prompt ) and in_array( Request::i()->prompt, array( 'login', 'reauthorize' ) ) )
{
\IPS\Db::i()->update( 'core_oauth_authorize_prompts', array( 'prompt' => \IPS\Request::i()->prompt ), array( 'session_id=?', \IPS\Request::i()->cookie['oauth_authorize'] ) );
$prompt = \IPS\Request::i()->prompt;
Db::i()->update( 'core_oauth_authorize_prompts', array( 'prompt' => Request::i()->prompt ), array( 'session_id=?', Request::i()->cookie['oauth_authorize'] ) );
$prompt = Request::i()->prompt;
}
}
catch ( \UnderflowException $e )
catch (UnderflowException $e )
{
throw new \IPS\Login\Handler\OAuth2\InitException('oauth_err_invalid_client');
throw new InitException('oauth_err_invalid_client');
}
}
else
{
$clientId = \IPS\Request::i()->client_id;
$responseType = \IPS\Request::i()->response_type;
$redirectUri = \IPS\Request::i()->redirect_uri;
$scope = \IPS\Request::i()->scope;
$state = \IPS\Request::i()->state;
$prompt = \IPS\Request::i()->prompt;
$codeChallenge = isset( \IPS\Request::i()->code_challenge ) ? \IPS\Request::i()->code_challenge : NULL;
$codeChallengeMethod = ( isset( \IPS\Request::i()->code_challenge_method ) and \in_array( \IPS\Request::i()->code_challenge_method, array( 'plain', 'S256' ) ) ) ? \IPS\Request::i()->code_challenge_method : NULL;
$clientId = Request::i()->client_id;
$responseType = Request::i()->response_type;
$redirectUri = Request::i()->redirect_uri;
$scope = Request::i()->scope;
$state = Request::i()->state;
$prompt = Request::i()->prompt;
$codeChallenge = isset( Request::i()->code_challenge ) ? Request::i()->code_challenge : NULL;
$codeChallengeMethod = ( isset( Request::i()->code_challenge_method ) and in_array( Request::i()->code_challenge_method, array( 'plain', 'S256' ) ) ) ? Request::i()->code_challenge_method : NULL;
}
/* Have we asked to register? */
if ( isset( \IPS\Request::i()->register ) )
if ( isset( Request::i()->register ) )
{
/* The authorize prompt data will probably expire before we're done, so put the referal URL (for after registration)
to the full URL which will initiate a new prompt. But don't delete the current prompt data in case the user hits back */
$url = \IPS\Http\Url::internal( 'oauth/authorize/', 'interface' )->setQueryString( array(
$url = Url::internal( 'oauth/authorize/', 'interface' )->setQueryString( array(
'client_id' => $clientId,
'response_type' => $responseType,
'redirect_uri' => $redirectUri,
@@ -520,7 +538,7 @@ try
'state' => $state,
'prompt' => ( $prompt === 'login' ) ? 'reauthorize' : $prompt, // We never need to log in immediately after registering, that's confusing
) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' )->addRef( (string) $url )->setQueryString( 'oauth', 1 ) );
Output::i()->redirect( Url::internal( 'app=core&module=system&controller=register', 'front', 'register' )->addRef( (string) $url )->setQueryString( 'oauth', 1 ) );
exit;
}
@@ -529,13 +547,13 @@ try
$request->validate();
/* If site is offline, return temporarily_unavailable */
if ( ( isset( \IPS\Settings::i()->setup_in_progress ) AND \IPS\Settings::i()->setup_in_progress ) or !\IPS\Settings::i()->site_online )
if ( ( isset( Settings::i()->setup_in_progress ) AND Settings::i()->setup_in_progress ) or !Settings::i()->site_online )
{
throw new \IPS\Login\Handler\OAuth2\Exception('temporarily_unavailable');
}
/* HTTPs only */
if ( \IPS\OAUTH_REQUIRES_HTTPS and !\IPS\Request::i()->isSecure() )
if ( OAUTH_REQUIRES_HTTPS and !Request::i()->isSecure() )
{
throw new \IPS\Login\Handler\OAuth2\Exception( 'invalid_request', "request must be made with https" );
}
@@ -555,16 +573,16 @@ try
}
/* Still here? Go ahead */
if ( isset( \IPS\Request::i()->cookie['oauth_authorize'] ) )
if ( isset( Request::i()->cookie['oauth_authorize'] ) )
{
\IPS\Db::i()->delete( 'core_oauth_authorize_prompts', array( 'session_id=?', \IPS\Request::i()->cookie['oauth_authorize'] ) );
\IPS\Request::i()->setCookie( 'oauth_authorize', NULL );
Db::i()->delete( 'core_oauth_authorize_prompts', array( 'session_id=?', Request::i()->cookie['oauth_authorize'] ) );
Request::i()->setCookie( 'oauth_authorize', NULL );
}
\IPS\Output::i()->redirect( $request->authorized( \IPS\Member::loggedIn(), isset( \IPS\Request::i()->grantedScope ) ? array_keys( \IPS\Request::i()->grantedScope ) : array() ), NULL, 302 );
Output::i()->redirect( $request->authorized( Member::loggedIn(), isset( Request::i()->grantedScope ) ? array_keys( Request::i()->grantedScope ) : array() ), NULL, 302 );
}
catch ( \IPS\Login\Handler\OAuth2\InitException $e )
catch ( InitException $e )
{
\IPS\Output::i()->error( $e->getMessage(), '2S361/2', 403 );
Output::i()->error( $e->getMessage(), '2S361/2', 403 );
}
catch ( \IPS\Login\Handler\OAuth2\Exception $e )
{
@@ -573,9 +591,9 @@ catch ( \IPS\Login\Handler\OAuth2\Exception $e )
{
$response['error_description'] = $e->description;
}
\IPS\Output::i()->redirect( $request->redirect( $response ), NULL, 302 );
Output::i()->redirect( $request->redirect( $response ), NULL, 302 );
}
catch ( Exception $e )
{
\IPS\Output::i()->redirect( $request->redirect( array( 'error' => 'server_error', 'error_description' => $e->getMessage() ) ), NULL, 302 );
Output::i()->redirect( $request->redirect( array( 'error' => 'server_error', 'error_description' => $e->getMessage() ) ), NULL, 302 );
}