Sidebar

Using iTop

Creating your iTop

iTop Customization

"How to" examples
DataModel

User Interface

Automation & Ticket management

Portal Customization

Offer new REST services

Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.

learning:
Create new REST services
level:
Intermediate
domains:
PHP
min version:
2.3.0

In this tutorial, we will see how we can enhanced the iTop Web Services by offering new services not yet covered by the core ones.

When defining web services, be aware that you can easily open security breaches

Knowing existing REST services in iTop, we can be limited

  • to access classes with category having neither grant-by-profile nor business. Examples of such classes: InlineImage, Attachment, TemplateFieldData,…
  • to execute a query which cannot be performed (easily) with the standard verbs, or require additional treatments.
    • for example get the mapping between a synchro-replica and the iTop object key,
    • For example, for getting a lot of data from iTop in order to compute a value and then store it in another iTop object with another REST call. You could instead create a new verb which does all in one call.
Be clear on what you want:
  • provided parameters,
  • returned data (think about access right),
  • volumes

Strategy

Within an itop extension XXXX

  1. Create a class which implements iRestServiceProvider (core/restservices.class.inc.php) with 2 methods:
    • ListOperations($sVersion)
    • ExecOperation($sVersion, $sVerb, $aParams)
  2. Define the services (=Operation =Verb)
  3. For each Service, code the behavior
  4. Define one or multiple classes extending RestResult class, depending on your service needs in terms of returned data.

Rules

  • The verb must be unique, so not colliding with any other verb brought by any other extension
  • iTop convention is to use <service_group>/<verb>
  • <service_group> must not be core which is reserved for iTop core.
  • For extending REST for a source application or partner, you can use the source application combined with your company name, to be sure to not collide with other extension, so for eg ldap-itomig
  • <verb> any name but make it meaningful

Entry parameters

  • Each verb decides which parameters are expected in entry and in which format
  • operation is a mandatory parameter
  • There is no order in the provided parameters
  • string, integer, boolean and array are the only possible type of param
  • A parameter can be optional or mandatory, but it's only visible in the PHP code of the service.
    • $sMandatoryParam1 = RestUtils::GetMandatoryParam($aParams, 'param1');
    • $sOptionalParam2 = RestUtils::GetOptionalParam($aParams, 'param2');
    • If mandatory parameter is not provided, the error treatment is handle by iRestServiceProvider for you.
  • Example core/get has 4 parameters operation, class,

Returned data

  • code: see standard values for allowed values
  • message can be empty
  • objects … a specific JSON structure containing the results for your service

The class RestResultWithObjects will handle the json data generation, here an example returning a set of objects $oObjectSet

ExecOperation
   $oResult = new RestResultWithObjects(); 
   while ($oObject = $oObjectSet->Fetch()) {
      $oResult->AddObject(0, '', $oObject, array('id','name','org_id','priority',...), false);
   }
   $oResult->message = "Found: ".$oObjectSet->Count();
   return $oResult;
}
If you returned a lot of objects and if they contains blobs, then the returned packet can quickly exceed memory_limit or other size constrains for the requestor

Caneva

As a proposal, you can reuse the caneva below, in which case

  • Enter in $aVerbs = [ the list of operations with description and php method to call
  • Keep ListOperations() and ExecOperation() as is
  • For each new Web Service that we want to offer, we will write a private PHP method to handle that Service
    • Read params
    • Check if the user is allowed to perform this action
    • Perform the requested service on iTop
    • Build Result object and return it
<?php
namespace Combodo\iTop\Extension\XXXX\Service;
 
// Add the classes you need for your services
use iRestServiceProvider;
use RestResult;
use RestUtils;
use Exception;
 
class RestService  implements iRestServiceProvider
{
//----------------- Define in this table your own web Services ------------
 
    private static $aVerbs = [
        'datasynchro/get_mappings' => [
            'description' => 'Return primary_key / dest_id mappings for a given DataSynchro',
            'method' => 'GetMappings',
        ],
        'request_template/get_details' => [
            'description' => 'Return service_details for a given Ticket',
            'method' => 'TemplateDetails',
        ],
    ];
//----------------- Keep the below 2 methods as is ---------------------------
    /**
     * Enumerate services delivered by this class
     * @param string $sVersion The version (e.g. 1.0) supported by the services
     * @return array An array of hash 'verb' => verb, 'description' => description
     */
    public function ListOperations($sVersion)
    {
        $aResult = [];
        foreach(static::$aVerbs as $sVerb => $aVerbDef) {
            $aResult[] = [
                'verb' => $sVerb,
                'description' => $aVerbDef['description'],
            ];
        }
        return $aResult;
    }
 
    /**
     * Executes the services delivered by this class
     *
     * @param string $sVersion The version (e.g. 1.0) supported by the services
     * @param string $sVerb
     * @param array $aParams
     *
     * @return RestResult The standardized result structure (at least a message)
     */
    public function ExecOperation($sVersion, $sVerb, $aParams)
    {
        if (!array_key_exists($sVerb, self::$aVerbs)) {
            $oResult = new RestResult();
            $oResult->code = RestResult::UNKNOWN_OPERATION;
            $oResult->message = "The operation '$sVerb' is not supported by this application.";
        } else {
            $sMethod = self::$aVerbs[$sVerb]['method'];
            $oResult = $this->$sMethod($aParams);
        }
        return $oResult;
    }
//----------- Define below your methods and RestResult classes -------------------
    /**
     * @param mixed[] $aParams
     * @return \Combodo\iTop\Extension\XXXX\Service\ExtraRestResult
     */
    private function GetMappings($aParams)
    {
        [...]
    }
 
    /**
     * @param mixed[] $aParams
     * @return \Combodo\iTop\Extension\XXXX\Service\ExtraRestResult
     */
    private function TemplateDetails($aParams)
    {
        // Prepare an object of the RestResult sub-class need for this service
        $oResult = new ExtraRestResult();
        // Read provided params
        $sSlug = RestUtils::GetMandatoryParam($aParams, 'slug');
        // Get Data from iTop
        $bBooked = xxxx($sSlug);
        // Store result
        $oResult->aData['booked'] = $bBooked;
        return $oResult;
    }
}
 
class ExtraRestResult extends RestResult
{
    /**
     * @var array
     */
    public $aData;
}

An example

 class ExampleGetInlineImageRest implements iRestServiceProvider
{
  public function ListOperations($sVersion)
  {
      $aOperations = array();
      $aOperations[] = array(
        'verb' => 'example/get_inlineimage',
        'description' => 'Return InlineImages used within a given "class" belonging to an "org_id"',
      );
      return $aOperations;
  }
 
  public function ExecOperation($sVersion, $sVerb, $aParams)
  {
          // Get the mandatory param
          $iOrgIDSource = RestUtils::GetMandatoryParam($aParams, 'org_id');   
          // Get an optional param - here it is useless                  
          $iClass = RestUtils::GetOptionalParam($aParams, 'class','');           
          $sInlineImageOQL = 'SELECT InlineImage WHERE item_org_id='
                             .$iOrgIDSource.' AND item_class="'.$iClass.'"';    
 
          // Search
          $oSearch = DBObjectSearch::FromOQL($sInlineImageOQL);
          $oSearch->AllowAllData() ;
          $oInlineImageSet = new CMDBObjectSet($oSearch);
 
          //list of fields to return
          $sFields = 'id,item_class,item_id,contents';
 
          // return feedback
          foreach(explode(',', $sFields) as $sAttCode)
         {
            $sAttCode = trim($sAttCode);
            if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode('InlineImage', $sAttCode)))
            {
              throw new Exception("Invalid attribute code '$sAttCode' for class InlineImage");
            }
            $aShowFields['InlineImage'][] = $sAttCode;
          }
 
 
        //objects to return
        $oResult = new RestResultWithObjects();
        while ($oInlineImage = $oInlineImageSet->Fetch())
        {
          $oResult->AddObject(0, '', $oInlineImage, $aShowFields, false);
        }
        $oResult->message = "Found: ".$oInlineImageSet->Count();
        return $oResult;
  }
 
}
latest/customization/add-rest-services.txt · Last modified: 2024/09/10 10:25 (external edit)
Back to top
Contact us