Skip to content
Snippets Groups Projects
class.api.php 13.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jared Hancock's avatar
    Jared Hancock committed
    <?php
    /*********************************************************************
        class.api.php
    
        API
    
        Peter Rotich <peter@osticket.com>
    
        Copyright (c)  2006-2013 osTicket
    
    Jared Hancock's avatar
    Jared Hancock committed
        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;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->load($id);
        }
    
    
        function load($id=0) {
    
            if(!$id && !($id=$this->getId()))
                return false;
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE id='.db_input($id);
    
            if(!($res=db_query($sql)) || !db_num_rows($res))
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            $this->ht = db_fetch_array($res);
            $this->id = $this->ht['id'];
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function reload() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $this->id;
        }
    
    
        function getKey() {
            return $this->ht['apikey'];
    
        function getIPAddr() {
            return $this->ht['ipaddr'];
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
        function getNotes() {
            return $this->ht['notes'];
    
        function getHashtable() {
            return $this->ht;
    
        function isActive() {
            return ($this->ht['isactive']);
        }
    
        function canCreateTickets() {
            return ($this->ht['can_create_tickets']);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function canExecuteCron() {
    
            return ($this->ht['can_exec_cron']);
    
        function update($vars, &$errors) {
    
            if(!API::save($this->getId(), $vars, $errors))
                return false;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            $sql='DELETE FROM '.API_KEY_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1';
            return (db_query($sql) && ($num=db_affected_rows()));
        }
    
        /** Static functions **/
    
        function add($vars, &$errors) {
            return API::save(0, $vars, $errors);
    
        function validate($key, $ip) {
            return ($key && $ip && self::getIdByKey($key, $ip));
    
            $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key);
            if($ip)
                $sql.=' AND ipaddr='.db_input($ip);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if(($res=db_query($sql)) && db_num_rows($res))
    
    Peter Rotich's avatar
    Peter Rotich committed
                list($id) = db_fetch_row($res);
    
        function lookupByKey($key, $ip='') {
            return self::lookup(self::getIdByKey($key, $ip));
    
        function lookup($id) {
            return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null;
        }
    
            if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr'])))
                $errors['ipaddr'] = 'Valid IP required';
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if($errors) return false;
    
    
            $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'])
    
                .',notes='.db_input(Format::sanitize($vars['notes']));
    
    Jared Hancock's avatar
    Jared Hancock committed
    
            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';
    
    
            } 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)))));
    
    Jared Hancock's avatar
    Jared Hancock committed
                if(db_query($sql) && ($id=db_insert_id()))
                    return $id;
    
    
                $errors['err']='Unable to add API key. Try again!';
    
    Jared Hancock's avatar
    Jared Hancock committed
            }
    
            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.
     */
    
    Jared Hancock's avatar
    Jared Hancock committed
    class ApiController {
    
        var $apikey;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function requireApiKey() {
            # Validate the API key -- required to be sent via the X-API-Key
            # header
    
    
            if(!($key=$this->getApiKey()))
    
                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');
    
    
            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;
    
    Jared Hancock's avatar
    Jared Hancock 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) {
    
    
            $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());
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            //Validate structure of the request.
            $this->validate($data, $format);
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $data;
        }
    
    
        function getEmailRequest() {
            return $this->getRequest('email');
        }
    
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        /**
         * Structure to validate the request against -- must be overridden to be
         * useful
         */
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getRequestStructure($format, $data=null) { return array(); }
    
    Jared Hancock's avatar
    Jared Hancock committed
        /**
         * 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="") {
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            foreach ($data as $key=>$info) {
                if (is_array($structure) and is_array($info)) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*";
    
    Jared Hancock's avatar
    Jared Hancock committed
                    if (isset($structure[$search])) {
    
                        $this->validateRequestStructure($info, $structure[$search], "$prefix$key/");
    
    Jared Hancock's avatar
    Jared Hancock committed
                        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(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $data,
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $this->getRequestStructure($format, $data)
    
    
        /**
         * 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;
    
            if($_SERVER['HTTP_X_API_KEY'])
    
                $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();
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
    }
    
    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'];
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            if (!is_array($current))
                return $current;
            foreach ($current as $key=>&$value) {
    
                if ($key == "phone" && is_array($value)) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $value = $value[":text"];
                } else if ($key == "alert") {
                    $value = (bool)$value;
                } else if ($key == "autorespond") {
                    $value = (bool)$value;
    
                } else if ($key == "message") {
                    if (!is_array($value)) {
                        $value = array(
                            "body" => $value,
                            "type" => "text/plain",
    
                            # Use encoding from root <xml> node
    
                        );
                    } else {
                        $value["body"] = $value[":text"];
                        unset($value[":text"]);
                    }
    
                    if (isset($value['encoding']))
                        $value['body'] = Format::utf8encode($value['body'], $value['encoding']);
    
                    // HTML-ize text if html is enabled
                    if ($cfg->isHtmlThreadEnabled()
                            && (!isset($value['type'])
                                || strcasecmp($value['type'], 'text/html')))
    
                        $value = sprintf('<pre>%s</pre>',
    
                            Format::htmlchars($value['body']));
    
                    // Text-ify html if html is disabled
                    elseif (!$cfg->isHtmlThreadEnabled()
                            && !strcasecmp($value['type'], 'text/html'))
                        $value = Format::html2text(Format::safe_html(
                            $value['body']), 100, false);
                    // Noop if they content-type matches the html setting
    
                    else
                        $value = $value['body'];
    
    Jared Hancock's avatar
    Jared Hancock committed
                } else if ($key == "attachments") {
    
                    if(!isset($value['file'][':text']))
                        $value = $value['file'];
    
                    if($value && is_array($value)) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                        foreach ($value as &$info) {
                            $info["data"] = $info[":text"];
    
                            unset($info[":text"]);
                        }
                        unset($info);
    
                } else if(is_array($value)) {
    
    Jared Hancock's avatar
    Jared Hancock committed
                    $value = $this->fixup($value);
                }
            }
    
            unset($value);
    
    Jared Hancock's avatar
    Jared Hancock committed
            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") {
    
    Jared Hancock's avatar
    Jared Hancock committed
                } else if ($key == "alert") {
                    $value = (bool)$value;
                } else if ($key == "autorespond") {
                    $value = (bool)$value;
    
                } elseif ($key == "message") {
                    // Allow message specified in RFC 2397 format
                    $data = Format::parseRfc2397($value, 'utf-8');
                    if (!isset($data['type']) || $data['type'] != 'text/html')
    
                        $value = sprintf('<pre>%s</pre>',
    
                            Format::htmlchars($data['data']));
                    else
                        $value = $data['data'];
    
    Jared Hancock's avatar
    Jared Hancock committed
                } else if ($key == "attachments") {
                    foreach ($value as &$info) {
                        $data = reset($info);
                        # PHP5: fopen("data://$data[5:]");
    
                        $contents = Format::parseRfc2397($data, 'utf-8', false);
                        $info = array(
                            "data" => $contents['data'],
                            "type" => $contents['type'],
                            "name" => key($info),
                        );
    
    Peter Rotich's avatar
    Peter Rotich committed
                }
    
    Jared Hancock's avatar
    Jared Hancock committed
                if (is_array($value)) {
                    $value = $this->fixup($value);
                }
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
            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;
        }
    }