Version 4.3.0
This commit is contained in:
1 parent
96997ddd8e
commit
fe1acf984a
1349 files changed
+116159
-67711
No files matched your search
@@ -55,6 +55,9 @@ class _Admin extends \IPS\Dispatcher\Standard
|
||||
public function init()
|
||||
{
|
||||
\IPS\Output::i()->sidebar['appmenu'] = '';
|
||||
|
||||
/* Disable sending of referrer to third party sites from AdminCP */
|
||||
\IPS\Output::i()->sendHeader( "Referrer-Policy: origin-when-cross-origin" );
|
||||
|
||||
/* Sync stuff when in developer mode */
|
||||
if ( \IPS\IN_DEV )
|
||||
@@ -124,7 +127,7 @@ class _Admin extends \IPS\Dispatcher\Standard
|
||||
{
|
||||
/* Make sure the right protocol is used. IIS, for example, does not like protocol relative URL's in redirects. (Ref: 970629) */
|
||||
$protocol = \IPS\Http\Url::PROTOCOL_HTTP;
|
||||
if ( \IPS\Settings::i()->logins_over_https OR \substr( \IPS\Settings::i()->base_url, 0, 5 ) == 'https' )
|
||||
if ( \substr( \IPS\Settings::i()->base_url, 0, 5 ) == 'https' )
|
||||
{
|
||||
$protocol = \IPS\Http\Url::PROTOCOL_HTTPS;
|
||||
}
|
||||
@@ -170,7 +173,20 @@ class _Admin extends \IPS\Dispatcher\Standard
|
||||
}
|
||||
|
||||
/* Permission Check */
|
||||
if ( ( $this->module->key !== 'system' or !in_array( $this->controller, array( 'login', 'language', 'theme', 'livesearch', 'editor', 'ajax' ) ) ) and ( $this->module->key !== 'members' or $this->controller !== 'members' or !in_array( \IPS\Request::i()->do, array( 'adminDetails', 'adminEmail', 'adminPassword' ) ) ) and !\IPS\Member::loggedIn()->hasAcpRestriction( $this->application, $this->module ) )
|
||||
if (
|
||||
(
|
||||
$this->module->key !== 'system' or
|
||||
!in_array( $this->controller, array( 'login', 'language', 'theme', 'livesearch', 'editor', 'ajax' ) )
|
||||
) and
|
||||
(
|
||||
$this->module->key !== 'members' or
|
||||
$this->controller !== 'members' or
|
||||
!in_array( \IPS\Request::i()->do, array( 'adminDetails', 'adminEmail', 'adminPassword' ) )
|
||||
) and
|
||||
/* This is slightly hacky, but the upgrader was moved to the system module, however the ACP restriction is still set to overview.
|
||||
To avoid unintentionally removing restrictions via an upgrade by moving the restriction, we reference the overview module for the restriction check instead */
|
||||
!\IPS\Member::loggedIn()->hasAcpRestriction( $this->application, ( $this->application->directory === 'core' and $this->module->key === 'system' and $this->controller === 'upgrade' ) ? \IPS\Application\Module::get( 'core', 'overview', 'admin' ) : $this->module )
|
||||
)
|
||||
{
|
||||
\IPS\Output::i()->error( 'no_module_permission', '2S107/1', 403, '' );
|
||||
}
|
||||
|
||||
+317
-140
@@ -27,6 +27,36 @@ class _Api extends \IPS\Dispatcher
|
||||
*/
|
||||
public $controllerLocation = 'api';
|
||||
|
||||
/**
|
||||
* @brief Path
|
||||
*/
|
||||
public $path = NULL;
|
||||
|
||||
/**
|
||||
* @brief Raw API Key
|
||||
*/
|
||||
public $rawApiKey = NULL;
|
||||
|
||||
/**
|
||||
* @brief Raw Access Token
|
||||
*/
|
||||
public $rawAccessToken = NULL;
|
||||
|
||||
/**
|
||||
* @brief API Key Object
|
||||
*/
|
||||
public $apiKey = NULL;
|
||||
|
||||
/**
|
||||
* @brief Access Token Details
|
||||
*/
|
||||
public $accessToken = NULL;
|
||||
|
||||
/**
|
||||
* @brief Language
|
||||
*/
|
||||
public $language = NULL;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
@@ -35,27 +65,60 @@ class _Api extends \IPS\Dispatcher
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
/* Get the path */
|
||||
$this->_setPath();
|
||||
|
||||
/* Check our IP address isn't banned */
|
||||
$this->_checkIpAddressIsAllowed();
|
||||
|
||||
/* Set our credentials */
|
||||
$this->_setRawCredentials();
|
||||
if ( $this->rawAccessToken )
|
||||
{
|
||||
$this->_setAccessToken();
|
||||
}
|
||||
elseif ( $this->rawApiKey )
|
||||
{
|
||||
$this->_setApiKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_API_KEY', '2S290/6', 401 );
|
||||
}
|
||||
|
||||
/* Set other data */
|
||||
$this->_setLanguage();
|
||||
}
|
||||
catch ( \IPS\Api\Exception $e )
|
||||
{
|
||||
/* Build resonse */
|
||||
$response = json_encode( array( 'errorCode' => $e->exceptionCode, 'errorMessage' => $e->getMessage() ), JSON_PRETTY_PRINT );
|
||||
|
||||
/* Do we need to log this? */
|
||||
if ( $this->rawApiKey !== 'test' and in_array( $e->exceptionCode, array( '2S290/8', '2S290/B', '3S290/7', '3S290/9' ) ) )
|
||||
{
|
||||
$this->_log( $response, $e->getCode(), in_array( $e->exceptionCode, array( '3S290/7', '3S290/9', '3S290/B' ) ) );
|
||||
}
|
||||
|
||||
/* Output */
|
||||
$this->_respond( $response, $e->getCode(), $e->oauthError );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run
|
||||
* Set the path and request data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
protected function _setPath()
|
||||
{
|
||||
/* Init */
|
||||
$shouldLog = FALSE;
|
||||
$isBadKey = FALSE;
|
||||
$output = NULL;
|
||||
$httpResponseCode = 500;
|
||||
|
||||
/* Decode URL */
|
||||
if ( \IPS\Settings::i()->use_friendly_urls and \IPS\Settings::i()->htaccess_mod_rewrite )
|
||||
if ( \IPS\Settings::i()->use_friendly_urls and \IPS\Settings::i()->htaccess_mod_rewrite and mb_substr( \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_PATH ], -14 ) !== '/api/index.php' )
|
||||
{
|
||||
/* We are using Mod Rewrite URL's, so look in the path */
|
||||
$path = mb_substr( \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_PATH ], mb_strpos( \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_PATH ], '/api/' ) + 5 );
|
||||
$this->path = mb_substr( \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_PATH ], mb_strpos( \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_PATH ], '/api/' ) + 5 );
|
||||
|
||||
/* nginx won't convert the 'fake' query string to $_GET params, so do this now */
|
||||
if ( ! empty( \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_QUERY ] ) )
|
||||
@@ -73,117 +136,212 @@ class _Api extends \IPS\Dispatcher
|
||||
else
|
||||
{
|
||||
/* Otherwise we are not, so we need the query string instead, which is actually easier */
|
||||
$path = \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_QUERY ];
|
||||
$this->path = \IPS\Request::i()->url()->data[ \IPS\Http\Url::COMPONENT_QUERY ];
|
||||
|
||||
/* However, if we passed any actual query string arguments, we need to strip those */
|
||||
if( mb_strpos( $path, '&' ) )
|
||||
if( mb_strpos( $this->path, '&' ) )
|
||||
{
|
||||
$path = mb_substr( $path, 0, mb_strpos( $path, '&' ) );
|
||||
$this->path = mb_substr( $this->path, 0, mb_strpos( $this->path, '&' ) );
|
||||
}
|
||||
}
|
||||
$pathBits = array_filter( explode( '/', $path ) );
|
||||
|
||||
/* Run */
|
||||
try
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if this is an API Key request, or an OAuth request
|
||||
*
|
||||
* @note OAuth requires Access Tokens only be transmitted over TLS, so if the request isn't secure, we ignore OAuth credentials
|
||||
* @return void
|
||||
*/
|
||||
public function _setRawCredentials()
|
||||
{
|
||||
/* Check if an API Key or Access Token has been passed as a parameter in the query string. Because of the
|
||||
obvious security issues with this, we do not recommend it, but sometimes it is the only choice */
|
||||
if ( isset( \IPS\Request::i()->key ) )
|
||||
{
|
||||
/* IP is banned? */
|
||||
if ( \IPS\Request::i()->ipAddressIsBanned() )
|
||||
$this->rawApiKey = \IPS\Request::i()->key;
|
||||
return;
|
||||
}
|
||||
if ( isset( \IPS\Request::i()->access_token ) and ( !\IPS\OAUTH_REQUIRES_HTTPS or \IPS\Request::i()->isSecure() ) )
|
||||
{
|
||||
$this->rawAccessToken = \IPS\Request::i()->access_token;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Look for an API key in an automatically decoded HTTP Basic header */
|
||||
if ( isset( $_SERVER['PHP_AUTH_USER'] ) )
|
||||
{
|
||||
$this->rawApiKey = $_SERVER['PHP_AUTH_USER'];
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we're still here, try to find an Authorization header - start with $_SERVER... */
|
||||
$authorizationHeader = NULL;
|
||||
foreach ( $_SERVER as $k => $v )
|
||||
{
|
||||
if ( mb_substr( $k, -18 ) == 'HTTP_AUTHORIZATION' )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'IP_ADDRESS_BANNED', '1S290/A', 403 );
|
||||
$authorizationHeader = $v;
|
||||
}
|
||||
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_api_logs', array( 'ip_address=? AND is_bad_key=1', \IPS\Request::i()->ipAddress() ) )->first() > 10 )
|
||||
}
|
||||
|
||||
/* ...if we didn't find anything there, try apache_request_headers() */
|
||||
if ( !$authorizationHeader and function_exists('apache_request_headers') )
|
||||
{
|
||||
$headers = @apache_request_headers();
|
||||
if ( isset( $headers['Authorization'] ) )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_banfilters', array(
|
||||
'ban_type' => 'ip',
|
||||
'ban_content' => \IPS\Request::i()->ipAddress(),
|
||||
'ban_date' => time(),
|
||||
'ban_reason' => 'API',
|
||||
) );
|
||||
unset( \IPS\Data\Store::i()->bannedIpAddresses );
|
||||
throw new \IPS\Api\Exception( 'IP_ADDRESS_BANNED', '1S290/A', 403 );
|
||||
$authorizationHeader = $headers['Authorization'];
|
||||
}
|
||||
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_api_logs', array( 'ip_address=? AND is_bad_key=1 AND date>?', \IPS\Request::i()->ipAddress(), \IPS\DateTime::create()->sub( new \DateInterval( 'PT5M' ) )->getTimestamp() ) )->first() > 1 )
|
||||
}
|
||||
|
||||
/* If we managed to get one, set if it's an API Key or an Access Token */
|
||||
if ( $authorizationHeader )
|
||||
{
|
||||
if ( mb_substr( $authorizationHeader, 0, 7 ) === 'Bearer ' and ( !\IPS\OAUTH_REQUIRES_HTTPS or \IPS\Request::i()->isSecure() ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'TOO_MANY_REQUESTS_WITH_BAD_KEY', '1S290/A', 429 );
|
||||
}
|
||||
|
||||
/* Work out API key */
|
||||
$rawApiKey = NULL;
|
||||
if ( \IPS\Request::i()->isCgi() and isset( \IPS\Request::i()->key ) )
|
||||
{
|
||||
$rawApiKey = \IPS\Request::i()->key;
|
||||
$this->rawAccessToken = mb_substr( $authorizationHeader, 7 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( $_SERVER['PHP_AUTH_USER'] ) )
|
||||
$exploded = explode( ':', base64_decode( mb_substr( $authorizationHeader, 6 ) ) );
|
||||
if ( isset( $exploded[0] ) )
|
||||
{
|
||||
$rawApiKey = $_SERVER['PHP_AUTH_USER'];
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( $_SERVER as $k => $v )
|
||||
{
|
||||
if ( mb_substr( $k, -18 ) == 'HTTP_AUTHORIZATION' )
|
||||
{
|
||||
$exploded = explode( ':', base64_decode( mb_substr( $v, 6 ) ) );
|
||||
if ( isset( $exploded[0] ) )
|
||||
{
|
||||
$rawApiKey = $exploded[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->rawApiKey = $exploded[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the IP Address isn't banned
|
||||
*
|
||||
* @return void
|
||||
* @throws \IPS\Api\Exception
|
||||
*/
|
||||
protected function _checkIpAddressIsAllowed()
|
||||
{
|
||||
/* Check the IP address is banned */
|
||||
if ( \IPS\Request::i()->ipAddressIsBanned() )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'IP_ADDRESS_BANNED', '1S290/A', 403 );
|
||||
}
|
||||
|
||||
/* If we have tried to access the API with a bad key more than 10 times, ban the IP address */
|
||||
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_api_logs', array( 'ip_address=? AND is_bad_key=1', \IPS\Request::i()->ipAddress() ) )->first() > 10 )
|
||||
{
|
||||
/* Remove the flag from these logs so that if the admin unbans the IP we aren't immediately banned again */
|
||||
\IPS\Db::i()->update( 'core_api_logs', array( 'is_bad_key' => 0 ), array( 'ip_address=?', \IPS\Request::i()->ipAddress() ) );
|
||||
|
||||
/* Then insert the ban... */
|
||||
\IPS\Db::i()->insert( 'core_banfilters', array(
|
||||
'ban_type' => 'ip',
|
||||
'ban_content' => \IPS\Request::i()->ipAddress(),
|
||||
'ban_date' => time(),
|
||||
'ban_reason' => 'API',
|
||||
) );
|
||||
unset( \IPS\Data\Store::i()->bannedIpAddresses );
|
||||
|
||||
/* And throw an error */
|
||||
throw new \IPS\Api\Exception( 'IP_ADDRESS_BANNED', '1S290/C', 403 );
|
||||
}
|
||||
|
||||
/* If we have tried to access the API with a bad key more than once in the last 5 minutes, throw an error to prevent brute-forcing */
|
||||
if ( \IPS\Db::i()->select( 'COUNT(*)', 'core_api_logs', array( 'ip_address=? AND is_bad_key=1 AND date>?', \IPS\Request::i()->ipAddress(), \IPS\DateTime::create()->sub( new \DateInterval( 'PT5M' ) )->getTimestamp() ) )->first() > 1 )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'TOO_MANY_REQUESTS_WITH_BAD_KEY', '1S290/D', 429 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set API Key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function _setApiKey()
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->apiKey = \IPS\Api\Key::load( $this->rawApiKey );
|
||||
|
||||
if ( $this->apiKey->allowed_ips and !in_array( \IPS\Request::i()->ipAddress(), explode( ',', $this->apiKey->allowed_ips ) ) )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'IP_ADDRESS_NOT_ALLOWED', '2S290/8', 403 );
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_API_KEY', '3S290/7', 401 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Access Token
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function _setAccessToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
$exploded = explode( '_', $this->rawAccessToken );
|
||||
|
||||
if ( !isset( $exploded[0] ) or !isset( $exploded[1] ) )
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
}
|
||||
|
||||
/* Get API key */
|
||||
if ( !isset( $rawApiKey ) )
|
||||
$this->accessToken = \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'client_id=? AND access_token=?', $exploded[0], $exploded[1] ) )->first();
|
||||
if ( $this->accessToken['access_token_expires'] and $this->accessToken['access_token_expires'] < time() )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_API_KEY', '2S290/6', 401 );
|
||||
throw new \IPS\Api\Exception( 'EXPIRED_ACCESS_TOKEN', '1S290/E', 401, 'invalid_token' );
|
||||
}
|
||||
try
|
||||
if ( !$this->accessToken['scope'] or !json_decode( $this->accessToken['scope'] ) )
|
||||
{
|
||||
$apiKey = \IPS\Api\Key::load( $rawApiKey );
|
||||
|
||||
if ( $rawApiKey != 'test' and $apiKey->allowed_ips and !in_array( \IPS\Request::i()->ipAddress(), explode( ',', $apiKey->allowed_ips ) ) )
|
||||
{
|
||||
$shouldLog = TRUE;
|
||||
throw new \IPS\Api\Exception( 'IP_ADDRESS_NOT_ALLOWED', '2S290/8', 403 );
|
||||
}
|
||||
|
||||
if ( $rawApiKey != 'test' and ! \IPS\Request::i()->isCgi() and isset( \IPS\Request::i()->key ) )
|
||||
{
|
||||
$shouldLog = TRUE;
|
||||
throw new \IPS\Api\Exception( 'CANNOT_USE_KEY_AS_URL_PARAM', '2S290/B', 403 );
|
||||
}
|
||||
throw new \IPS\Api\Exception( 'NO_SCOPES', '3S290/B', 401, 'insufficient_scope' );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ACCESS_TOKEN', '3S290/9', 401, 'invalid_token' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Language
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function _setLanguage()
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( isset( $_SERVER['HTTP_X_IPS_LANGUAGE'] ) )
|
||||
{
|
||||
if ( $rawApiKey != 'test' )
|
||||
{
|
||||
$shouldLog = TRUE;
|
||||
$isBadKey = TRUE;
|
||||
}
|
||||
throw new \IPS\Api\Exception( 'INVALID_API_KEY', '3S290/7', 401 );
|
||||
$this->language = \IPS\Lang::load( intval( $_SERVER['HTTP_X_IPS_LANGUAGE'] ) );
|
||||
}
|
||||
|
||||
/* Get language */
|
||||
try
|
||||
else
|
||||
{
|
||||
if ( isset( $_SERVER['HTTP_X_IPS_LANGUAGE'] ) )
|
||||
{
|
||||
$language = \IPS\Lang::load( intval( $_SERVER['HTTP_X_IPS_LANGUAGE'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$language = \IPS\Lang::load( \IPS\Lang::defaultLanguage() );
|
||||
}
|
||||
$this->language = \IPS\Lang::load( \IPS\Lang::defaultLanguage() );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_LANGUAGE', '2S290/9', 400 );
|
||||
}
|
||||
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_LANGUAGE', '2S290/9', 400, 'invalid_request' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$shouldLog = FALSE;
|
||||
try
|
||||
{
|
||||
/* Work out the app and controller. Both can only be alphanumeric - prevents include injections */
|
||||
$pathBits = array_filter( explode( '/', $this->path ) );
|
||||
$app = array_shift( $pathBits );
|
||||
if ( !preg_match( '/^[a-z0-9]+$/', $app ) )
|
||||
{
|
||||
@@ -219,61 +377,79 @@ class _Api extends \IPS\Dispatcher
|
||||
}
|
||||
|
||||
/* Run it */
|
||||
$controller = new $class( $apiKey );
|
||||
$controller = new $class( $this->apiKey, $this->accessToken );
|
||||
$response = $controller->execute( $pathBits, $shouldLog );
|
||||
|
||||
/* Output */
|
||||
$output = json_encode( $response->getOutput(), JSON_PRETTY_PRINT );
|
||||
$language->parseOutputForDisplay( $output );
|
||||
$httpResponseCode = $response->httpCode;
|
||||
|
||||
/* Send Output */
|
||||
$output = json_encode( $response->getOutput(), JSON_PRETTY_PRINT );
|
||||
$this->language->parseOutputForDisplay( $output );
|
||||
$this->_respond( $output, $response->httpCode, NULL, $shouldLog, TRUE );
|
||||
}
|
||||
catch ( \IPS\Api\Exception $e )
|
||||
{
|
||||
if ( $shouldLog )
|
||||
{
|
||||
\IPS\Log::log( $e, 'api' );
|
||||
}
|
||||
|
||||
$output = json_encode( array( 'errorCode' => $e->exceptionCode, 'errorMessage' => $e->getMessage() ), JSON_PRETTY_PRINT );
|
||||
$httpResponseCode = $e->getCode();
|
||||
$this->_respond( json_encode( array( 'errorCode' => $e->exceptionCode, 'errorMessage' => $e->getMessage() ), JSON_PRETTY_PRINT ), $e->getCode(), $e->oauthError, $shouldLog );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
if ( $shouldLog )
|
||||
{
|
||||
\IPS\Log::log( $e, 'api' );
|
||||
}
|
||||
|
||||
$output = json_encode( array( 'errorCode' => 'EX' . $e->getCode(), 'errorMessage' => \IPS\IN_DEV ? $e->getMessage() : 'UNKNOWN_ERROR' ), JSON_PRETTY_PRINT );
|
||||
$httpResponseCode = 500;
|
||||
\IPS\Log::log( $e, 'api' );
|
||||
|
||||
$this->_respond( json_encode( array( 'errorCode' => 'EX' . $e->getCode(), 'errorMessage' => \IPS\IN_DEV ? $e->getMessage() : 'UNKNOWN_ERROR' ), JSON_PRETTY_PRINT ), 500 );
|
||||
}
|
||||
|
||||
/* Log? */
|
||||
if ( $shouldLog )
|
||||
{
|
||||
/* Database logging errors should not prevent the output being sent */
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_api_logs', array(
|
||||
'endpoint' => $path,
|
||||
'method' => $_SERVER['REQUEST_METHOD'],
|
||||
'api_key' => $rawApiKey,
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'request_data' => json_encode( $_REQUEST, JSON_PRETTY_PRINT ),
|
||||
'response_code' => $httpResponseCode,
|
||||
'response_output' => $output,
|
||||
'date' => time(),
|
||||
'is_bad_key' => $isBadKey
|
||||
) );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e ) {}
|
||||
}
|
||||
|
||||
/* Output */
|
||||
\IPS\Output::i()->sendOutput( $output, $httpResponseCode, 'application/json' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log
|
||||
*
|
||||
* @param array $response Response to output
|
||||
* @param int $httpResponseCode HTTP Response Code
|
||||
* @param bool $isBadKey Was the ley invalid?
|
||||
* @return void
|
||||
*/
|
||||
protected function _log( $response, $httpResponseCode, $isBadKey=FALSE )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_api_logs', array(
|
||||
'endpoint' => $this->path,
|
||||
'method' => $_SERVER['REQUEST_METHOD'],
|
||||
'api_key' => $this->rawApiKey,
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'request_data' => json_encode( $_REQUEST, JSON_PRETTY_PRINT ),
|
||||
'response_code' => $httpResponseCode,
|
||||
'response_output' => $response,
|
||||
'date' => time(),
|
||||
'is_bad_key' => $isBadKey,
|
||||
'client_id' => $this->accessToken ? $this->accessToken['client_id'] : NULL,
|
||||
'member_id' => $this->accessToken ? $this->accessToken['member_id'] : NULL,
|
||||
'access_token' => $this->rawAccessToken,
|
||||
) );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e ) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output response
|
||||
*
|
||||
* @param string $response Response to output
|
||||
* @param int $httpResponseCode HTTP Response Code
|
||||
* @param bool $parseLanguage If response should be language parsed
|
||||
*/
|
||||
protected function _respond( $response, $httpResponseCode, $oauthError=NULL, $log=FALSE )
|
||||
{
|
||||
$headers = array();
|
||||
if ( $this->rawAccessToken and $oauthError )
|
||||
{
|
||||
$headers['WWW-Authenticate'] = "Bearer error=\"{$oauthError}\"";
|
||||
}
|
||||
|
||||
if ( $log )
|
||||
{
|
||||
$this->_log( $response, $httpResponseCode );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->sendOutput( $response, $httpResponseCode, 'application/json', $headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*
|
||||
@@ -281,5 +457,6 @@ class _Api extends \IPS\Dispatcher
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -77,12 +77,8 @@ class _Build extends \IPS\Dispatcher
|
||||
|
||||
\IPS\Data\Store::i()->builder_building = time();
|
||||
}
|
||||
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => 0 ), array( 'conf_key=?', 'site_online' ) );
|
||||
\IPS\Settings::i()->site_online = 0;
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
|
||||
\IPS\Settings::i()->changeValues( array( 'site_online' => 0 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,11 +92,7 @@ class _Build extends \IPS\Dispatcher
|
||||
{
|
||||
unset( \IPS\Data\Store::i()->builder_building );
|
||||
}
|
||||
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
|
||||
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => 1 ), array( 'conf_key=?', 'site_online' ) );
|
||||
\IPS\Settings::i()->site_online = 1;
|
||||
unset( \IPS\Data\Store::i()->settings );
|
||||
\IPS\Settings::i()->changeValues( array( 'site_online' => 1 ) );
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,8 @@ abstract class _Controller
|
||||
*/
|
||||
protected function embed()
|
||||
{
|
||||
$title = \IPS\Member::loggedIn()->language()->addToStack('error_title');
|
||||
$title = \IPS\Member::loggedIn()->language()->addToStack('error_title');
|
||||
$content = null;
|
||||
|
||||
if( !isset( static::$contentModel ) )
|
||||
{
|
||||
@@ -198,6 +199,18 @@ abstract class _Controller
|
||||
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, $embedCss );
|
||||
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/embeds.css', 'core', 'front' ) );
|
||||
|
||||
/* Seo Stuffs */
|
||||
\IPS\Output::i()->title = $title;
|
||||
|
||||
if( $content !== NULL )
|
||||
{
|
||||
\IPS\Output::i()->linkTags['canonical'] = (string) $content->url();
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Output::i()->metaTags['robots'] = 'noindex';
|
||||
}
|
||||
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->embedInternal( $output, $js ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
|
||||
}
|
||||
}
|
||||
+45
-32
@@ -36,17 +36,26 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
{
|
||||
/* Get cached page if available */
|
||||
$this->checkCached();
|
||||
|
||||
|
||||
/* Set up in progress? */
|
||||
if ( isset( \IPS\Settings::i()->setup_in_progress ) AND \IPS\Settings::i()->setup_in_progress )
|
||||
{
|
||||
$protocol = '1.0';
|
||||
if( isset( $_SERVER['SERVER_PROTOCOL'] ) and \strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) !== false )
|
||||
{
|
||||
header( "HTTP/1.0 503 Service Unavailable" );
|
||||
$protocol = '1.1';
|
||||
}
|
||||
|
||||
if ( \IPS\CIC and ! \IPS\Session\Front::loggedIn() and ! \IPS\Session\Front::i()->userAgent->spider )
|
||||
{
|
||||
/* The software is unavailable, but the site is up so we do not want to affect our cloud downtime statistics and trigger monitoring alarms
|
||||
if we are not a search engine */
|
||||
header( "HTTP/{$protocol} 200 OK" );
|
||||
}
|
||||
else
|
||||
{
|
||||
header( "HTTP/1.1 503 Service Unavailable" );
|
||||
header( "HTTP/{$protocol} 503 Service Unavailable" );
|
||||
header( "Retry-After: 300"); #5 minutes
|
||||
}
|
||||
|
||||
require \IPS\ROOT_PATH . '/' . \IPS\UPGRADING_PAGE;
|
||||
@@ -93,7 +102,7 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
parent::init();
|
||||
}
|
||||
catch ( \DomainException $e )
|
||||
{
|
||||
{
|
||||
// If this is a "no permission", and they're validating - show the validating screen instead
|
||||
if( $e->getCode() === 6 and \IPS\Member::loggedIn()->member_id and \IPS\Member::loggedIn()->members_bitoptions['validating'] )
|
||||
{
|
||||
@@ -163,7 +172,7 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
}
|
||||
|
||||
/* Do we need more info from the member or do they need to validate? */
|
||||
if( \IPS\Member::loggedIn()->member_id and $this->controller !== 'language' and $this->controller !== 'theme' )
|
||||
if( \IPS\Member::loggedIn()->member_id and $this->controller !== 'language' and $this->controller !== 'theme' and $this->controller !== 'ajax' )
|
||||
{
|
||||
/* Need their name or email... */
|
||||
if( ( \IPS\Member::loggedIn()->real_name === '' or !\IPS\Member::loggedIn()->email ) and $this->controller !== 'register' )
|
||||
@@ -181,7 +190,7 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=reconfirm', 'front', 'register' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
|
||||
}
|
||||
/* Have required profile actions that need completing */
|
||||
else if ( !\IPS\Member::loggedIn()->members_bitoptions['profile_completed'] AND !in_array( $this->controller, array( 'register', 'login', 'redirect', 'contact', 'privacy', 'ajax', 'settings' ) ) AND $completion = \IPS\Member::loggedIn()->profileCompletion() AND count( $completion['required'] ) )
|
||||
else if ( \IPS\Settings::i()->quick_register AND !\IPS\Member::loggedIn()->members_bitoptions['profile_completed'] AND !in_array( $this->controller, array( 'register', 'login', 'redirect', 'contact', 'privacy', 'ajax', 'settings' ) ) AND $completion = \IPS\Member::loggedIn()->profileCompletion() AND count( $completion['required'] ) )
|
||||
{
|
||||
foreach( $completion['required'] AS $id => $completed )
|
||||
{
|
||||
@@ -231,7 +240,7 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
{
|
||||
if ( !\IPS\Member::loggedIn()->member_id and isset( \IPS\Request::i()->_mfaLogin ) )
|
||||
{
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login', NULL, \IPS\Settings::i()->logins_over_https )->setQueryString( '_mfaLogin', 1 ) );
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
|
||||
}
|
||||
\IPS\Output::i()->error( ( \IPS\Member::loggedIn()->member_id ? 'no_module_permission' : 'no_module_permission_guest' ), '2S100/2', 403, 'no_module_permission_admin' );
|
||||
}
|
||||
@@ -260,21 +269,18 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
}
|
||||
|
||||
/* Figure out what the global search is */
|
||||
if ( !$this->application->default )
|
||||
foreach ( $this->application->extensions( 'core', 'ContentRouter' ) as $object )
|
||||
{
|
||||
foreach ( $this->application->extensions( 'core', 'ContentRouter' ) as $object )
|
||||
if ( count( $object->classes ) === 1 )
|
||||
{
|
||||
if ( count( $object->classes ) === 1 )
|
||||
$classes = $object->classes;
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
$classes = $object->classes;
|
||||
foreach ( $classes as $class )
|
||||
if ( is_subclass_of( $class, 'IPS\Content\Searchable' ) and $class::includeInSiteSearch() and $this->module->key == $class::$module )
|
||||
{
|
||||
if ( is_subclass_of( $class, 'IPS\Content\Searchable' ) and $class::includeInSiteSearch() )
|
||||
{
|
||||
$type = mb_strtolower( str_replace( '\\', '_', mb_substr( array_pop( $classes ), 4 ) ) );
|
||||
\IPS\Output::i()->defaultSearchOption = array( $type, "{$type}_pl" );
|
||||
break;
|
||||
}
|
||||
$type = mb_strtolower( str_replace( '\\', '_', mb_substr( array_pop( $classes ), 4 ) ) );
|
||||
\IPS\Output::i()->defaultSearchOption = array( $type, "{$type}_pl" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,12 +377,25 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
*/
|
||||
protected function checkCached()
|
||||
{
|
||||
/* If we are using a caching engine and Database datastore, then avoid checking cache validity because it will hit MySQL to load the cacheKeys. As this is guest caching, we accept that stale data is ok */
|
||||
if ( \IPS\STORE_METHOD === 'Database' AND \IPS\CACHE_METHOD !== 'None' )
|
||||
/* Store page view in Redis */
|
||||
if ( \IPS\CACHE_METHOD == 'Redis' and \IPS\CIC and !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\Data\Store::i()->cacheCheck = false;
|
||||
try
|
||||
{
|
||||
if( \IPS\Redis::i() === NULL )
|
||||
{
|
||||
throw new \RedisException( 'Redis instance is NULL', -1 );
|
||||
}
|
||||
|
||||
\IPS\Redis::i()->logPageHit();
|
||||
}
|
||||
catch( \RedisException $e )
|
||||
{
|
||||
/* Sometimes the connection can be reset - we don't want to let an uncaught exception bubble up in those cases */
|
||||
\IPS\Log::log( $e, 'redis' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* If this is a guest and there's a full cached page, we can serve that */
|
||||
if( mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' and !isset( \IPS\Request::i()->csrfKey ) and !isset( \IPS\Request::i()->_mfaLogin ) and !\IPS\Session\Front::loggedIn() and !isset( \IPS\Request::i()->cookie['noCache'] ) and !\IPS\Request::i()->ipAddressIsBanned() and \IPS\CACHE_PAGE_TIMEOUT )
|
||||
{
|
||||
@@ -408,12 +427,6 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
\IPS\Output::i()->sendOutput( str_replace( '{{csrfKey}}', md5( '& 0&' . $sessionId ), $cache['output'] ), $cache['code'], $cache['contentType'], $cache['httpHeaders'], FALSE, TRUE );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) {}
|
||||
|
||||
/* Turn back on cache validation if we are still here */
|
||||
if ( \IPS\STORE_METHOD === 'Database' AND \IPS\CACHE_METHOD !== 'None' )
|
||||
{
|
||||
\IPS\Data\Store::i()->cacheCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,7 +494,7 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
|
||||
if( count( $widgets ) )
|
||||
{
|
||||
if ( \IPS\CACHE_METHOD === 'None' and ! \IPS\Theme::isUsingTemplateDiskCache() )
|
||||
if ( ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and ! \IPS\Theme::isUsingTemplateDiskCache() )
|
||||
{
|
||||
$templateLoad = array();
|
||||
foreach ( $widgets as $areaKey => $area )
|
||||
@@ -517,8 +530,8 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
continue;
|
||||
}
|
||||
|
||||
$_widget = \IPS\Widget::load( $appOrPlugin, $widget['key'], ( ! empty($widget['unique'] ) ? $widget['unique'] : uniqid() ), ( isset( $widget['configuration'] ) ) ? $widget['configuration'] : array(), ( isset( $widget['restrict'] ) ? $widget['restrict'] : null ), ( $areaKey == 'sidebar' ) ? 'vertical' : 'horizontal' );
|
||||
if ( \IPS\CACHE_METHOD === 'None' and isset( $_widget->cacheKey ) )
|
||||
$_widget = \IPS\Widget::load( $appOrPlugin, $widget['key'], ( ! empty($widget['unique'] ) ? $widget['unique'] : mt_rand() ), ( isset( $widget['configuration'] ) ) ? $widget['configuration'] : array(), ( isset( $widget['restrict'] ) ? $widget['restrict'] : null ), ( $areaKey == 'sidebar' ) ? 'vertical' : 'horizontal' );
|
||||
if ( ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and isset( $_widget->cacheKey ) )
|
||||
{
|
||||
$storeLoad[] = $_widget->cacheKey;
|
||||
}
|
||||
@@ -531,7 +544,7 @@ class _Front extends \IPS\Dispatcher\Standard
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\CACHE_METHOD === 'None' and count( $storeLoad ) )
|
||||
if( ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and count( $storeLoad ) )
|
||||
{
|
||||
\IPS\Data\Store::i()->loadIntoMemory( $storeLoad );
|
||||
}
|
||||
|
||||
@@ -223,11 +223,11 @@ class _Setup extends \IPS\Dispatcher
|
||||
/* If we're still here - output */
|
||||
if ( \IPS\Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( \IPS\Output::i()->output ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( \IPS\Output::i()->output ), 200, 'text/html', \IPS\Output::i()->httpHeaders, FALSE, FALSE, TRUE, FALSE );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( \IPS\Output::i()->title, \IPS\Output::i()->output ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( \IPS\Output::i()->title, \IPS\Output::i()->output ), 200, 'text/html', \IPS\Output::i()->httpHeaders, FALSE, FALSE, TRUE, FALSE );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,10 @@ abstract class _Standard extends \IPS\Dispatcher
|
||||
\IPS\Output::i()->jsVars['ipb_url_filter_option'] = \IPS\Settings::i()->ipb_url_filter_option;
|
||||
\IPS\Output::i()->jsVars['url_filter_any_action'] = \IPS\Settings::i()->url_filter_any_action;
|
||||
\IPS\Output::i()->jsVars['bypass_profanity'] = intval( \IPS\Member::loggedIn()->group['g_bypass_badwords'] );
|
||||
\IPS\Output::i()->jsVars['emoji_style'] = \IPS\Settings::i()->getFromConfGlobal('sql_utf8mb4') ? \IPS\Settings::i()->emoji_style : 'disabled';
|
||||
\IPS\Output::i()->jsVars['emoji_shortcodes'] = \IPS\Settings::i()->emoji_shortcodes;
|
||||
\IPS\Output::i()->jsVars['emoji_ascii'] = \IPS\Settings::i()->emoji_ascii;
|
||||
\IPS\Output::i()->jsVars['emoji_cache'] = \IPS\Settings::i()->emoji_cache;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +231,7 @@ abstract class _Standard extends \IPS\Dispatcher
|
||||
if ( !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
/* Templates */
|
||||
if ( \IPS\CACHE_METHOD === 'None' )
|
||||
if ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None )
|
||||
{
|
||||
\IPS\Data\Store::i()->templateLoad[] = array( 'core', $this->controllerLocation, 'global' );
|
||||
\IPS\Data\Store::i()->templateLoad[] = array( 'core', 'global', 'global' );
|
||||
|
||||
Reference in new issue
Block a user