Source of file WebServiceController.php
Size: 11,079 Bytes - Last Modified: 2021-12-23T10:36:19+00:00
/var/www/docs.ssmods.com/process/src/code/controllers/WebServiceController.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 | <?php /** * Controller designed to wrap around calls to defined services * * To call a service, use jsonservice/servicename/methodname * * @author marcus@silverstripe.com.au * @license BSD License http://silverstripe.org/bsd-license/ */ class WebServiceController extends Controller { /** * List of object -> json converter classes * * @var array */ protected $converters = array(); protected $format = 'json'; private static $dependencies = array( 'webserviceAuthenticator' => '%$WebserviceAuthenticator', 'injector' => '%$Injector', ); /** * @var WebserviceAuthenticator */ public $webserviceAuthenticator; /** * @var Injector */ public $injector; public function init() { $this->converters['json'] = array( 'DataObject' => new DataObjectJsonConverter(), 'DataObjectSet' => new DataObjectSetJsonConverter(), 'DataList' => new DataObjectSetJsonConverter(), 'ArrayList' => new DataObjectSetJsonConverter(), 'Array' => new ArrayJsonConverter(), 'ScalarItem' => new ScalarJsonConverter(), 'stdClass' => new ScalarJsonConverter(), 'FinalConverter' => new FinalJsonConverter() ); $this->converters['xml'] = array( 'DataObject' => new DataObjectXmlConverter(), 'DataObjectSet' => new DataObjectSetXmlConverter(), 'DataList' => new DataObjectSetXmlConverter(), 'ArrayList' => new DataObjectSetXmlConverter(), 'Array' => new ArrayXmlConverter(), 'ScalarItem' => new ScalarXmlConverter(), 'stdClass' => new ScalarXmlConverter(), 'FinalConverter' => new FinalXmlConverter() ); if (strpos($this->request->getURL(), 'xmlservice') === 0) { $this->format = 'xml'; } } public function handleRequest(SS_HTTPRequest $request, DataModel $model) { try { $this->pushCurrent(); $auth = $this->webserviceAuthenticator->authenticate($request); if (!$auth) { throw new WebServiceException(403, 'User not found'); } // borrowed from Controller $this->urlParams = $request->allParams(); $this->request = $request; $this->response = new SS_HTTPResponse(); $this->setDataModel($model); $this->extend('onBeforeInit'); $this->init(); $this->extend('onAfterInit'); if($this->response->isFinished()) { $this->popCurrent(); return $this->response; } $response = $this->handleService($request, $model); if (self::has_curr()) { $this->popCurrent(); } if ($response instanceof SS_HTTPResponse) { $response->addHeader('Content-Type', 'application/'.$this->format); } // HTTP::add_cache_headers($this->response); return $response; } catch (WebServiceException $exception) { $this->response = new SS_HTTPResponse(); $this->response->setStatusCode($exception->status); $this->response->setBody($this->ajaxResponse($exception->getMessage(), $exception->status)); } catch (SS_HTTPResponse_Exception $e) { $this->response = $e->getResponse(); $this->response->setBody($this->ajaxResponse($e->getMessage(), $e->getCode())); } catch (Exception $exception) { $code = 500; // check type explicitly in case the Restricted Objects module isn't installed if (class_exists('PermissionDeniedException') && $exception instanceof PermissionDeniedException) { $code = 403; } $this->response = new SS_HTTPResponse(); $this->response->setStatusCode($code); $this->response->setBody($this->ajaxResponse($exception->getMessage(), $code)); } return $this->response; } /** * Calls to webservices are routed through here and converted * from url params to method calls. * * @return mixed */ public function handleService() { $service = ucfirst($this->request->param('Service')) . 'Service'; $method = $this->request->param('Method'); $body = $this->request->getBody(); $requestType = strlen($body) > 0 ? 'POST' : $this->request->httpMethod(); // (count($this->request->postVars()) > 0 ? 'POST' : 'GET'); $svc = $this->injector->get($service); $response = ''; if ($svc && ($svc instanceof WebServiceable || method_exists($svc, 'webEnabledMethods'))) { $allowedMethods = array(); if (method_exists($svc, 'webEnabledMethods')) { $allowedMethods = $svc->webEnabledMethods(); } // if we have a list of methods, lets use those to restrict if (count($allowedMethods)) { $this->checkMethods($method, $allowedMethods, $requestType); } else { // we only allow 'read only' requests so we wrap everything // in a readonly transaction so that any database request // disallows write() calls // @TODO } if (!Member::currentUserID()) { // require service to explicitly state that the method is allowed if (method_exists($svc, 'publicWebMethods')) { $publicMethods = $svc->publicWebMethods(); if (!isset($publicMethods[$method])) { throw new WebServiceException(403, "Public method $method not allowed"); } } else { throw new WebServiceException(403, "Method $method not allowed; no public methods defined"); } } $refObj = new ReflectionObject($svc); $refMeth = $refObj->getMethod($method); /* @var $refMeth ReflectionMethod */ if ($refMeth) { $allArgs = $this->getRequestArgs($requestType); $refParams = $refMeth->getParameters(); $params = array(); foreach ($refParams as $refParm) { /* @var $refParm ReflectionParameter */ $paramClass = $refParm->getClass(); // if we're after a dataobject, we'll try and find one using // this name with ID and Type parameters if ($paramClass && ($paramClass->getName() == 'DataObject' || $paramClass->isSubclassOf('DataObject'))) { $idArg = $refParm->getName().'ID'; $typeArg = $refParm->getName().'Type'; if (isset($allArgs[$idArg]) && isset($allArgs[$typeArg]) && class_exists($allArgs[$typeArg])) { $object = null; if (class_exists('DataService')) { $object = $this->injector->DataService->byId($allArgs[$typeArg], $allArgs[$idArg]); } else { $object = DataObject::get_by_id($allArgs[$typeArg], $allArgs[$idArg]); if (!$object->canView()) { $object = null; } } if ($object) { $params[$refParm->getName()] = $object; } } else { $params[$refParm->getName()] = null; } } else if (isset($allArgs[$refParm->getName()])) { $params[$refParm->getName()] = $allArgs[$refParm->getName()]; } else if ($refParm->getName() == 'file' && $requestType == 'POST') { // special case of a binary file upload $params['file'] = $body; } else if ($refParm->isOptional()) { $params[$refParm->getName()] = $refParm->getDefaultValue(); } else { throw new WebServiceException(500, "Service method $method expects parameter " . $refParm->getName()); } } $return = $refMeth->invokeArgs($svc, $params); $responseItem = $this->convertResponse($return); $response = $this->converters[$this->format]['FinalConverter']->convert($responseItem); } } $this->response->setBody($response); return $this->response; } /** * Process a request URL + body to get all parameters for a request * * @param string $requestType * @return array */ protected function getRequestArgs($requestType = 'GET') { if ($requestType == 'GET') { $allArgs = $this->request->getVars(); } else { $allArgs = $this->request->postVars(); } unset($allArgs['url']); $contentType = strtolower($this->request->getHeader('Content-Type')); if (strpos($contentType, 'application/json') !== false && !count($allArgs) && strlen($this->request->getBody())) { // decode the body to a params array $bodyParams = Convert::json2array($this->request->getBody()); if (isset($bodyParams['params'])) { $allArgs = $bodyParams['params']; } else { $allArgs = $bodyParams; } } // see if there's any other URL bits to chew up $remaining = $this->request->remaining(); $bits = explode('/', $remaining); for ($i = 0, $c = count($bits); $i < $c; ) { $key = $bits[$i]; $val = isset($bits[$i + 1]) ? $bits[$i + 1] : null; if ($val && !isset($allArgs[$key])) { $allArgs[$key] = $val; } $i += 2; } return $allArgs; } /** * Check the allowed methods for access rights * * @param array $allowedMethods * @throws WebServiceException */ protected function checkMethods($method, $allowedMethods, $requestType) { if (!isset($allowedMethods[$method])) { throw new WebServiceException(403, "You do not have permission to $method"); } $info = $allowedMethods[$method]; $allowedType = $info; if (is_array($info)) { $allowedType = isset($info['type']) ? $info['type'] : ''; if (isset($info['perm'])) { if (!Permission::check($info['perm'])) { throw new WebServiceException(403, "You do not have permission to $method"); } } } // otherwise it might be the wrong request type if ($requestType != $allowedType) { throw new WebServiceException(405, "$method does not support $requestType"); } } /** * Converts the given object to something appropriate for a response */ public function convertResponse($return) { if (is_object($return)) { $cls = get_class($return); } else if (is_array($return)) { $cls = 'Array'; } else { $cls = 'ScalarItem'; } if (isset($this->converters[$this->format][$cls])) { return $this->converters[$this->format][$cls]->convert($return, $this); } // otherwise, check the hierarchy if the class actually exists if (class_exists($cls)) { $hierarchy = array_reverse(array_keys(ClassInfo::ancestry($cls))); foreach ($hierarchy as $cls) { if (isset($this->converters[$this->format][$cls])) { return $this->converters[$this->format][$cls]->convert($return, $this); } } } return $return; } /** * Indicate whether public users can access web services in general * * @param boolean $value */ public static function set_allow_public($value) { self::$allow_public_access = $value; } protected function ajaxResponse($message, $status) { return Convert::raw2json(array( 'message' => $message, 'status' => $status, )); } } class WebServiceException extends Exception { public $status; public function __construct($status=403, $message='', $code=null, $previous=null) { $this->status = $status; parent::__construct($message, $code, $previous); } } class ScalarJsonConverter { public function convert($value) { return Convert::raw2json($value); } } class ScalarXmlConverter { public function convert($value) { return '<value>' . Convert::raw2xml($value) . '</value>'; } } class FinalJsonConverter { public function convert($value) { $return = '{"response": '.$value . '}'; return $return; } } class FinalXmlConverter { public function convert($value) { $return = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . '<response>'.$value.'</response>'; return $return; } } |