FundacionLQDVI_WebCongresos/www/components/com_akeeba/models/json.php

657 lines
17 KiB
PHP

<?php
/**
* @package AkeebaBackup
* @copyright Copyright (c)2006-2010 Nicholas K. Dionysopoulos
* @license GNU General Public License version 3, or later
* @version $Id: json.php 303 2010-11-17 12:24:26Z nikosdion $
* @since 3.0
*/
// Protect from unauthorized access
defined('_JEXEC') or die('Restricted Access');
// JSON API version number
define('AKEEBA_JSON_API_VERSION', '300');
// Load framework base classes
jimport('joomla.application.component.model');
include_once JPATH_COMPONENT_ADMINISTRATOR.DS.'plugins'.DS.'helpers'.DS.'encrypt.php';
if(!defined('AKEEBA_BACKUP_ORIGIN'))
{
define('AKEEBA_BACKUP_ORIGIN','remote');
}
class AkeebaModelJson extends JModel
{
const STATUS_OK = 200; // Normal reply
const STATUS_NOT_AUTH = 401; // Invalid credentials
const STATUS_NOT_ALLOWED = 403; // Not enough privileges
const STATUS_NOT_FOUND = 404; // Requested resource not found
const STATUS_INVALID_METHOD = 405; // Unknown JSON method
const STATUS_ERROR = 500; // An error occured
const STATUS_NOT_IMPLEMENTED = 501; // Not implemented feature
const STATUS_NOT_AVAILABLE = 503; // Remote service not activated
const ENCAPSULATION_RAW = 1; // Data in plain-text JSON
const ENCAPSULATION_AESCTR128 = 2; // Data in AES-128 stream (CTR) mode encrypted JSON
const ENCAPSULATION_AESCTR256 = 3; // Data in AES-256 stream (CTR) mode encrypted JSON
const ENCAPSULATION_AESCBC128 = 4; // Data in AES-128 standard (CBC) mode encrypted JSON
const ENCAPSULATION_AESCBC256 = 5; // Data in AES-256 standard (CBC) mode encrypted JSON
private $json_errors = array(
JSON_ERROR_NONE => 'No error has occurred (probably emtpy data passed)',
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error'
);
/** @var int The status code */
private $status = 200;
/** @var int Data encapsulation format */
private $encapsulation = 1;
/** @var mixed Any data to be returned to the caller */
private $data = '';
/** @var string A password passed to us by the caller */
private $password = null;
public function execute($json)
{
// Check if we're activated
$enabled = AEPlatform::get_platform_configuration_option('frontend_enable', 0);
if(!$enabled)
{
$this->data = 'Access denied';
$this->status = self::STATUS_NOT_AVAILABLE;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $this->getResponse();
}
// Try to JSON-decode the request's input first
$request = @json_decode($json, false);
if(is_null($request))
{
// Could not decode JSON
$this->data = 'JSON decoding error: '.$this->json_errors[json_last_error()];
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $this->getResponse();
}
// Decode the request body
// Request format: {encapsulation, body{ [key], [challenge], method, [data] }} or {[challenge], method, [data]}
if( isset($request->encapsulation) && isset($request->body) )
{
if(!class_exists('AkeebaHelperEncrypt') && !($request->encapsulation == self::ENCAPSULATION_RAW))
{
// Encrypted request found, but there is no encryption class available!
$this->data = 'This server does not support encrypted requests';
$this->status = self::STATUS_NOT_AVAILABLE;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $this->getResponse();
}
// Fully specified request
switch( $request->encapsulation )
{
case self::ENCAPSULATION_AESCBC128:
if(!isset($body))
{
$request->body = base64_decode($request->body);
$body = AkeebaHelperEncrypt::AESDecryptCBC($request->body, $this->serverKey(), 128);
}
break;
case self::ENCAPSULATION_AESCBC256:
if(!isset($body))
{
$request->body = base64_decode($request->body);
$body = AkeebaHelperEncrypt::AESDecryptCBC($request->body, $this->serverKey(), 256);
}
break;
case self::ENCAPSULATION_AESCTR128:
if(!isset($body))
{
$body = AkeebaHelperEncrypt::AESDecryptCtr($request->body, $this->serverKey(), 128);
}
break;
case self::ENCAPSULATION_AESCTR256:
if(!isset($body))
{
$body = AkeebaHelperEncrypt::AESDecryptCtr($request->body, $this->serverKey(), 256);
}
break;
case self::ENCAPSULATION_RAW:
$body = $request->body;
break;
}
if(!empty($request->body))
{
$body = rtrim( $body, chr(0) );
$request->body = json_decode($body);
if(is_null($request->body))
{
// Decryption failed. The user is an imposter! Go away, hacker!
$this->data = 'Authentication failed';
$this->status = self::STATUS_NOT_AUTH;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $this->getResponse();
}
}
}
elseif( isset($request->body) )
{
// Partially specified request, assume RAW encapsulation
$request->encapsulation = self::ENCAPSULATION_RAW;
$request->body = json_decode($request->body);
}
else
{
// Legacy request
$legacyRequest = clone $request;
$request = (object) array( 'encapsulation' => self::ENCAPSULATION_RAW, 'body' => null );
$request->body = json_decode($legacyRequest);
unset($legacyRequest);
}
// Authenticate the user. Do note that if an encrypted request was made, we can safely assume that
// the user is authenticated (he already knows the server key!)
if($request->encapsulation == self::ENCAPSULATION_RAW)
{
$authenticated = false;
if(isset($request->body->challenge))
{
list($challenge,$check) = explode(':', $request->body->challenge);
$crosscheck = strtolower(md5($challenge.$this->serverKey()));
$authenticated = ($crosscheck == $check);
}
if(!$authenticated)
{
// If the challenge was missing or it was wrong, don't let him go any further
$this->data = 'Invalid login credentials';
$this->status = self::STATUS_NOT_AUTH;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $this->getResponse();
}
}
// Replicate the encapsulation preferences of the client for our own output
$this->encapsulation = $request->encapsulation;
// Store the client-specified key, or use the server key if none specified and the request
// came encrypted.
$this->password = isset($request->body->key) ? $request->body->key : null;
if(is_null($request->body->key) && ($request->encapsulation != self::ENCAPSULATION_RAW) )
{
$this->password = $this->serverKey();
}
// Does the specified method exist?
$method_exists = false;
$method_name = '';
if(isset($request->body->method))
{
$method_name = ucfirst($request->body->method);
$method_exists = method_exists($this, '_api'.$method_name );
}
if(!$method_exists)
{
// The requested method doesn't exist. Oops!
$this->data = "Invalid method $method_name";
$this->status = self::STATUS_INVALID_METHOD;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $this->getResponse();
}
// Run the method
$params = array();
if(isset($request->body->data)) $params = (array)$request->body->data;
$this->data = call_user_func( array($this, '_api'.$method_name) , $params);
return $this->getResponse();
}
/**
* Packages the response to a JSON-encoded object, optionally encrypting the
* data part with a caller-supplied password.
* @return string The JSON-encoded response
*/
private function getResponse()
{
// Initialize the response
$response = array(
'encapsulation' => $this->encapsulation,
'body' => array(
'status' => $this->status,
'data' => null
)
);
$data = json_encode($this->data);
if(empty($this->password)) $this->encapsulation = self::ENCAPSULATION_RAW;
switch($this->encapsulation)
{
case self::ENCAPSULATION_RAW:
break;
case self::ENCAPSULATION_AESCTR128:
$data = AkeebaHelperEncrypt::AESEncryptCtr($data, $this->password, 128);
break;
case self::ENCAPSULATION_AESCTR256:
$data = AkeebaHelperEncrypt::AESEncryptCtr($data, $this->password, 256);
break;
case self::ENCAPSULATION_AESCBC128:
$data = base64_encode(AkeebaHelperEncrypt::AESEncryptCBC($data, $this->password, 128));
break;
case self::ENCAPSULATION_AESCBC256:
$data = base64_encode(AkeebaHelperEncrypt::AESEncryptCBC($data, $this->password, 256));
break;
}
$response['body']['data'] = $data;
return '###' . json_encode($response) . '###';
}
private function serverKey()
{
static $key = null;
if(is_null($key))
{
$key = AEPlatform::get_platform_configuration_option('frontend_secret_word', '');
}
return $key;
}
private function _apiGetVersion()
{
return (object)array(
'api' => AKEEBA_JSON_API_VERSION,
'component' => AKEEBA_VERSION,
'date' => AKEEBA_DATE
);
}
private function _apiGetProfiles()
{
require_once JPATH_SITE.DS.'administrator'.DS.'components'.DS.'com_akeeba'.DS.'models'.DS.'profiles.php';
$model = new AkeebaModelProfiles();
$profiles = $model->getProfilesList(true);
$ret = array();
if(count($profiles))
{
foreach($profiles as $profile)
{
$temp = new stdClass();
$temp->id = $profile->id;
$temp->name = $profile->description;
$ret[] = $temp;
}
}
return $ret;
}
private function _apiStartBackup($config)
{
$defConfig = array(
'profile' => 1,
'description' => '',
'comment' => ''
);
$config = array_merge($defConfig, $config);
extract($config);
// Set the profile
$profile = JRequest::getInt('profile',1);
if(!is_numeric($profile)) $profile = 1;
$session =& JFactory::getSession();
$session->set('profile', $profile, 'akeeba');
AEPlatform::load_configuration($profile);
// Use the default description if none specified
if(empty($description))
{
jimport('joomla.utilities.date');
$user =& JFactory::getUser();
$userTZ = $user->getParam('timezone',0);
$dateNow = new JDate();
$dateNow->setOffset($userTZ);
if( AKEEBA_JVERSION == '16' ) {
$description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->format(JText::_('DATE_FORMAT_LC2'), true);
} else {
$description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->toFormat(JText::_('DATE_FORMAT_LC2'));
}
}
// Start the backup
AECoreKettenrad::reset();
$memory_filename = AEUtilTempvars::get_storage_filename(AKEEBA_BACKUP_ORIGIN);
@unlink($memory_filename);
$kettenrad =& AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN);
$options = array(
'description' => $description,
'comment' => $comment
);
$kettenrad->setup($options);
$array = $kettenrad->tick();
AECoreKettenrad::save(AKEEBA_BACKUP_ORIGIN);
$array = $kettenrad->getStatusArray();
if($array['Error'] != '')
{
// A backup error had occured. Why are we here?!
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'A backup error had occured: '.$array['Error'];
}
else
{
$array = $kettenrad->tick();
if($array['Error'] != '')
{
// A backup error had occured. Why are we here?!
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'A backup error had occured: '.$array['Error'];
}
else
{
$statistics =& AEFactory::getStatistics();
$array['BackupID'] = $statistics->getId();
return $array;
}
}
}
private function _apiStepBackup($config)
{
$defConfig = array(
);
$config = array_merge($defConfig, $config);
extract($config);
$kettenrad =& AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN);
$array = $kettenrad->getStatusArray();
if($array['Error'] != '')
{
// A backup error had occured. Why are we here?!
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'A backup error had occured: '.$array['Error'];
}
elseif($array['HasRun'] == 1)
{
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'The backup is already finalized!';
}
else
{
$array = $kettenrad->tick();
AECoreKettenrad::save();
if($array['Error'] != '')
{
// A backup error had occured. Why are we here?!
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'A backup error had occured: '.$array['Error'];
}
else
{
return $array;
}
}
}
private function _apiListBackups($config)
{
$defConfig = array(
'from' => 0,
'limit' => 50
);
$config = array_merge($defConfig, $config);
extract($config);
require_once JPATH_COMPONENT_ADMINISTRATOR.DS.'models'.DS.'statistics.php';
$model = new AkeebaModelStatistics();
return $model->getStatisticsListWithMeta(true);
}
private function _apiGetBackupInfo($config)
{
$defConfig = array(
'backup_id' => '0'
);
$config = array_merge($defConfig, $config);
extract($config);
// Get the basic statistics
$record = AEPlatform::get_statistics($backup_id);
// Get a list of filenames
$backup_stats = AEPlatform::get_statistics($backup_id);
// Backup record doesn't exist
if(empty($backup_stats))
{
$this->status = self::STATUS_NOT_FOUND;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'Invalid backup record identifier';
}
$filenames = AEUtilStatistics::get_all_filenames($stat);
if(empty($filenames))
{
// Archives are not stored on the server or no files produced
$record['filenames'] = array();
}
else
{
$filedata = array();
$i = 0;
// Get file sizes per part
foreach($filenames as $file)
{
$i++;
$size = @filesize($file);
$size = is_numeric($size) ? $size : 0;
$filedata[] = array(
'part' => $i,
'name' => basename($file),
'size' => $size
);
}
// Add the file info to $record['filenames']
$record['filenames'] = $filedata;
}
return $record;
}
private function _apiDownload($config)
{
$defConfig = array(
'backup_id' => 0,
'part_id' => 1,
'segment' => 1
);
$config = array_merge($defConfig, $config);
extract($config);
$backup_stats = AEPlatform::get_statistics($backup_id);
if(empty($backup_stats))
{
// Backup record doesn't exist
$this->status = self::STATUS_NOT_FOUND;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'Invalid backup record identifier';
}
$files = AEUtilStatistics::get_all_filenames($backup_stats);
if( (count($files) < $part_id) || ($part_id <= 0) )
{
// Invalid part
$this->status = self::STATUS_NOT_FOUND;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'Invalid backup part';
}
$file = $files[$segment-1];
$fp = fopen($file, 'rb');
if($fp === false)
{
// Could not read file
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'Error reading backup archive';
}
$seekPos = 1048576 * ($segment - 1);
if($seekPos>0) if(!fseek($fp, $seekPos))
{
// Could not seek to position
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'Error reading specified segment';
}
$buffer = fread($fp, 1048756);
if($buffer === false)
{
// Could not read
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return 'Error reading specified segment';
}
fclose($fp);
switch($this->encapsulation)
{
case self::ENCAPSULATION_RAW:
return base64_encode($buffer);
break;
case self::ENCAPSULATION_AESCTR128:
$this->encapsulation = self::ENCAPSULATION_AESCBC128;
return $buffer;
break;
case self::ENCAPSULATION_AESCTR256:
$this->encapsulation = self::ENCAPSULATION_AESCBC256;
return $buffer;
break;
default:
// On encrypted comms the encryption will take care of transport encoding
return $buffer;
break;
}
}
private function _apiDelete($config)
{
$defConfig = array(
'backup_id' => 0
);
$config = array_merge($defConfig, $config);
extract($config);
require_once JPATH_COMPONENT_ADMINISTRATOR.DS.'models'.DS.'statistics.php';
$model = new AkeebaModelStatistics();
$result = $model->delete((int)$backup_id);
if(!$result)
{
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $model->getError();
}
else
{
return true;
}
}
private function _apiDeleteFiles($config)
{
$defConfig = array(
'backup_id' => 0
);
$config = array_merge($defConfig, $config);
extract($config);
require_once JPATH_COMPONENT_ADMINISTRATOR.DS.'models'.DS.'statistics.php';
$model = new AkeebaModelStatistics();
$result = $model->deleteFile((int)$backup_id);
if(!$result)
{
$this->status = self::STATUS_ERROR;
$this->encapsulation = self::ENCAPSULATION_RAW;
return $model->getError();
}
else
{
return true;
}
}
private function _apiGetLog($config)
{
$defConfig = array(
'tag' => 'remote'
);
$config = array_merge($defConfig, $config);
extract($config);
$filename = AEUtilLogger::logName($tag);
$buffer = file_get_contents($filename);
switch($this->encapsulation)
{
case self::ENCAPSULATION_RAW:
return base64_encode($buffer);
break;
case self::ENCAPSULATION_AESCTR128:
$this->encapsulation = self::ENCAPSULATION_AESCBC128;
return $buffer;
break;
case self::ENCAPSULATION_AESCTR256:
$this->encapsulation = self::ENCAPSULATION_AESCBC256;
return $buffer;
break;
default:
// On encrypted comms the encryption will take care of transport encoding
return $buffer;
break;
}
}
}