Newer
Older
<?php
/*********************************************************************
class.api.php
API
Peter Rotich <peter@osticket.com>
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class API {
var $id;
Peter Rotich
committed
var $ht;
Peter Rotich
committed
function API($id) {
$this->id = 0;
Peter Rotich
committed
function load($id=0) {
if(!$id && !($id=$this->getId()))
return false;
$sql='SELECT * FROM '.API_KEY_TABLE.' WHERE id='.db_input($id);
Peter Rotich
committed
if(!($res=db_query($sql)) || !db_num_rows($res))
return false;
Peter Rotich
committed
$this->ht = db_fetch_array($res);
$this->id = $this->ht['id'];
Peter Rotich
committed
return true;
Peter Rotich
committed
return $this->load();
Peter Rotich
committed
function getId() {
Peter Rotich
committed
function getKey() {
return $this->ht['apikey'];
Peter Rotich
committed
function getIPAddr() {
return $this->ht['ipaddr'];
Peter Rotich
committed
function getNotes() {
return $this->ht['notes'];
Peter Rotich
committed
function getHashtable() {
return $this->ht;
Peter Rotich
committed
function isActive() {
return ($this->ht['isactive']);
}
function canCreateTickets() {
return ($this->ht['can_create_tickets']);
}
return ($this->ht['can_exec_cron']);
Peter Rotich
committed
function update($vars, &$errors) {
if(!API::save($this->getId(), $vars, $errors))
return false;
Peter Rotich
committed
$this->reload();
return true;
Peter Rotich
committed
function delete() {
$sql='DELETE FROM '.API_KEY_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1';
return (db_query($sql) && ($num=db_affected_rows()));
}
/** Static functions **/
Peter Rotich
committed
function add($vars, &$errors) {
return API::save(0, $vars, $errors);
Peter Rotich
committed
function validate($key, $ip) {
return ($key && $ip && self::getIdByKey($key, $ip));
Peter Rotich
committed
function getIdByKey($key, $ip='') {
Peter Rotich
committed
$sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key);
if($ip)
$sql.=' AND ipaddr='.db_input($ip);
if(($res=db_query($sql)) && db_num_rows($res))
Peter Rotich
committed
return $id;
Peter Rotich
committed
function lookupByKey($key, $ip='') {
return self::lookup(self::getIdByKey($key, $ip));
Peter Rotich
committed
function lookup($id) {
return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null;
}
Peter Rotich
committed
function save($id, $vars, &$errors) {
Peter Rotich
committed
if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr'])))
$errors['ipaddr'] = 'Valid IP required';
Peter Rotich
committed
$sql=' updated=NOW() '
.',isactive='.db_input($vars['isactive'])
.',can_create_tickets='.db_input($vars['can_create_tickets'])
.',can_exec_cron='.db_input($vars['can_exec_cron'])
Peter Rotich
committed
.',notes='.db_input($vars['notes']);
if($id) {
$sql='UPDATE '.API_KEY_TABLE.' SET '.$sql.' WHERE id='.db_input($id);
if(db_query($sql))
return true;
$errors['err']='Unable to update API key. Internal error occurred';
Peter Rotich
committed
} else {
$sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql
.',created=NOW() '
.',ipaddr='.db_input($vars['ipaddr'])
.',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randCode(16)))));
Peter Rotich
committed
if(db_query($sql) && ($id=db_insert_id()))
return $id;
Peter Rotich
committed
$errors['err']='Unable to add API key. Try again!';
}
return false;
}
}
/**
* Controller for API methods. Provides methods to check to make sure the
* API key was sent and that the Client-IP and API-Key have been registered
* in the database, and methods for parsing and validating data sent in the
* API request.
*/
Peter Rotich
committed
function requireApiKey() {
# Validate the API key -- required to be sent via the X-API-Key
# header
return $this->exerr(401, 'Valid API key required');
elseif (!$key->isActive() || $key->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
return $this->exerr(401, 'API key not found/active or source IP not authorized');
Peter Rotich
committed
return $key;
Peter Rotich
committed
function getApiKey() {
if (!$this->apikey && isset($_SERVER['HTTP_X_API_KEY']) && isset($_SERVER['REMOTE_ADDR']))
$this->apikey = API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']);
return $this->apikey;
Peter Rotich
committed
}
/**
* Retrieves the body of the API request and converts it to a common
* hashtable. For JSON formats, this is mostly a noop, the conversion
* work will be done for XML requests
*/
function getRequest($format) {
global $ost;
$input = $ost->is_cli()?'php://stdin':'php://input';
if (!($stream = @fopen($input, 'r')))
$this->exerr(400, "Unable to read request body");
$parser = null;
switch(strtolower($format)) {
case 'xml':
if (!function_exists('xml_parser_create'))
$this->exerr(501, 'XML extension not supported');
$parser = new ApiXmlDataParser();
break;
case 'json':
$parser = new ApiJsonDataParser();
break;
case 'email':
$parser = new ApiEmailDataParser();
break;
default:
$this->exerr(415, 'Unsupported data format');
if (!($data = $parser->parse($stream)))
$this->exerr(400, $parser->lastError());
//Validate structure of the request.
$this->validate($data, $format);
function getEmailRequest() {
return $this->getRequest('email');
}
/**
* Structure to validate the request against -- must be overridden to be
* useful
*/
function getRequestStructure($format) { return array(); }
/**
* Simple validation that makes sure the keys of a parsed request are
* expected. It is assumed that the functions actually implementing the
* API will further validate the contents of the request
*/
function validateRequestStructure($data, $structure, $prefix="") {
foreach ($data as $key=>$info) {
if (is_array($structure) and is_array($info)) {
$search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*";
$this->validateRequestStructure($info, $structure[$search], "$prefix$key/");
continue;
}
} elseif (in_array($key, $structure)) {
continue;
}
return $this->exerr(400, "$prefix$key: Unexpected data received");
return true;
}
/**
* Validate request.
*
*/
function validate(&$data, $format) {
return $this->validateRequestStructure(
$this->getRequestStructure($format)
);
/**
* API error & logging and response!
*
*/
/* If possible - DO NOT - overwrite the method downstream */
function exerr($code, $error='') {
global $ost;
if($error && is_array($error))
$error = Format::array_implode(": ", "\n", $error);
//Log the error as a warning - include api key if available.
$msg = $error;
$msg.="\n*[".$_SERVER['HTTP_X_API_KEY']."]*\n";
$ost->logWarning("API Error ($code)", $msg, false);
$this->response($code, $error); //Responder should exit...
return false;
}
//Default response method - can be overwritten in subclasses.
function response($code, $resp) {
Http::response($code, $resp);
exit();
}
}
include_once "class.xml.php";
class ApiXmlDataParser extends XmlDataParser {
function parse($stream) {
return $this->fixup(parent::parse($stream));
}
/**
* Perform simple operations to make data consistent between JSON and
* XML data types
*/
function fixup($current) {
if($current['ticket'])
$current = $current['ticket'];
if (!is_array($current))
return $current;
foreach ($current as $key=>&$value) {
if ($key == "phone" && is_array($value)) {
if (isset($value['ext']))
$current["phone_ext"] = $value["ext"]; # PHP [like] point
$value = $value[":text"];
} else if ($key == "alert") {
$value = (bool)$value;
} else if ($key == "autorespond") {
$value = (bool)$value;
} else if ($key == "attachments") {
if(!isset($value['file'][':text']))
$value = $value['file'];
if($value && is_array($value)) {
foreach ($value as &$info) {
$info["data"] = $info[":text"];
unset($info[":text"]);
}
unset($info);
} else if(is_array($value)) {
return $current;
}
}
include_once "class.json.php";
class ApiJsonDataParser extends JsonDataParser {
function parse($stream) {
return $this->fixup(parent::parse($stream));
}
function fixup($current) {
if (!is_array($current))
return $current;
foreach ($current as $key=>&$value) {
if ($key == "phone") {
Peter Rotich
committed
list($value, $current["phone_ext"])
} else if ($key == "alert") {
$value = (bool)$value;
} else if ($key == "autorespond") {
$value = (bool)$value;
} else if ($key == "attachments") {
foreach ($value as &$info) {
$data = reset($info);
# PHP5: fopen("data://$data[5:]");
if (substr($data, 0, 5) != "data:") {
$info = array(
"type" => "text/plain",
"name" => key($info));
} else {
$data = substr($data,5);
list($meta, $contents) = explode(",", $data);
list($type, $extra) = explode(";", $meta);
$info = array(
"data" => $contents,
"type" => ($type) ? $type : "text/plain",
if (substr($extra, -6) == "base64")
$info["encoding"] = "base64";
# Handle 'charset' hint in $extra, such as
# data:text/plain;charset=iso-8859-1,Blah
# Convert to utf-8 since it's the encoding scheme
# for the database. Otherwise, assume utf-8
list($param,$charset) = explode('=', $extra);
if (is_array($value)) {
$value = $this->fixup($value);
}
}
return $current;
}
}
/* Email parsing */
include_once "class.mailparse.php";
class ApiEmailDataParser extends EmailDataParser {
function parse($stream) {
return $this->fixup(parent::parse($stream));
}
function fixup($data) {
global $cfg;
if(!$data) return $data;
$data['source'] = 'Email';
if(!$data['message'])
$data['message'] = $data['subject']?$data['subject']:'-';
if(!$data['subject'])
$data['subject'] = '[No Subject]';
if(!$data['emailId'])
$data['emailId'] = $cfg->getDefaultEmailId();
if(!$cfg->useEmailPriority())
unset($data['priorityId']);
return $data;
}
}