/** * validates all options that have been defined throwing rpc gateway faults if any of the * options fail to validate. see constant descriptions for what each constant does * * @error 14015 * @param string $option expects the option name * @param null|mixed $value expects the options value * @throws Xapp_Rpc_Gateway_Exception * @throws Xapp_Rpc_Fault */ protected function validate($option, $value = null) { $user = null; $option = strtoupper($option); switch ($option) { case self::BASIC_AUTH: if ($this->request()->has('PHP_AUTH_USER', 'SERVER') && $this->request()->has('PHP_AUTH_PW', 'SERVER') && isset($value[0]) && isset($value[1])) { if ($this->server()->request()->getFrom('PHP_AUTH_USER', 'SERVER') !== $value[0] || $this->server()->request()->getFrom('PHP_AUTH_PW', 'SERVER') !== $value[1]) { Xapp_Rpc_Fault::t("basic auth error - user or password not correct", array(1401501, -32001)); } } else { Xapp_Rpc_Fault::t("basic auth error - credentials not set", array(1401502, -32002)); } break; case self::ALLOW_IP: if (Xapp_Rpc_Request::getClientIp() !== null && !in_array(Xapp_Rpc_Request::getClientIp(), $value)) { Xapp_Rpc_Fault::t("request denied from service", array(1401503, -32003)); } break; case self::DENY_IP: if (Xapp_Rpc_Request::getClientIp() !== null && in_array(Xapp_Rpc_Request::getClientIp(), $value)) { Xapp_Rpc_Fault::t("request denied from service", array(1401503, -32003)); } break; case self::DISABLE: if ((bool) $value) { Xapp_Rpc_Fault::t("gateway is disabled", array(1401504, -32004)); } break; case self::DISABLE_SERVICE: if ($this->server()->hasServices()) { foreach ($this->server()->getServices() as $service) { if (preg_match(Xapp_Rpc::regex($value), $service)) { Xapp_Rpc_Fault::t("requested service: {$service} is disabled", array(1401505, -32005)); } } } break; case self::ALLOW_HOST: if (Xapp_Rpc_Request::getHost() !== null && !in_array(Xapp_Rpc_Request::getHost(), $value)) { Xapp_Rpc_Fault::t("host denied from service", array(1401506, -32006)); } break; case self::DENY_HOST: if (Xapp_Rpc_Request::getHost() !== null && in_array(Xapp_Rpc_Request::getHost(), $value)) { Xapp_Rpc_Fault::t("host denied from service", array(1401506, -32006)); } break; case self::FORCE_HTTPS: if ((bool) $value && Xapp_Rpc_Request::getScheme() !== 'HTTPS') { Xapp_Rpc_Fault::t("request from none http secure host denied", array(1401507, -32007)); } break; case self::ALLOW_USER_AGENT: if ($this->request()->has('HTTP_USER_AGENT', 'SERVER') && !preg_match('/(' . implode('|', trim($value, '()')) . ')/i', $this->request()->getFrom('HTTP_USER_AGENT', 'SERVER'))) { Xapp_Rpc_Fault::t("client denied from service", array(1401508, -32008)); } break; case self::DENY_USER_AGENT: if ($this->request()->has('HTTP_USER_AGENT', 'SERVER') && preg_match('/(' . implode('|', trim($value, '()')) . ')/i', $this->request()->getFrom('HTTP_USER_AGENT', 'SERVER'))) { Xapp_Rpc_Fault::t("client denied from service", array(1401508, -32008)); } break; case self::ALLOW_REFERER: if (Xapp_Rpc_Request::getReferer() !== null && !preg_match('/(' . implode('|', trim($value, '()')) . ')/i', Xapp_Rpc_Request::getReferer())) { Xapp_Rpc_Fault::t("referer denied from service", array(1401509, -32009)); } break; case self::SIGNED_REQUEST: if ((bool) $value) { $tmp = array(); if (xapp_is_option(self::SIGNED_REQUEST_EXCLUDES, $this)) { foreach ($this->server()->getServices() as $service) { if (!preg_match(Xapp_Rpc::regex(xapp_get_option(self::SIGNED_REQUEST_EXCLUDES, $this)), $service)) { $tmp[] = $service; } } } if (sizeof($tmp) > 0) { $sign = $this->request()->getParam(xapp_get_option(self::SIGNED_REQUEST_SIGN_PARAM, $this), false); $method = strtolower(xapp_get_option(self::SIGNED_REQUEST_METHOD, $this)); switch ($method) { case 'host': $user = $this->request()->getHost(); break; case 'ip': $user = $this->request()->getClientIp(); break; case 'user': $user = $this->request()->getParam(xapp_get_option(self::SIGNED_REQUEST_USER_PARAM, $this), false); break; default: throw new Xapp_Rpc_Gateway_Exception(_("unsupported signed request user identification method"), 1401514); } if ($user === false || $user === null) { Xapp_Rpc_Fault::t(vsprintf("signed request value for: %s not found in request", array(xapp_get_option(self::SIGNED_REQUEST_USER_PARAM, $this))), array(1401512, -32011)); } if ($sign === false || $sign === null) { Xapp_Rpc_Fault::t(vsprintf("signed request value for: %s not found in request", array(xapp_get_option(self::SIGNED_REQUEST_SIGN_PARAM, $this))), array(1401513, -32011)); } $key = $this->getKey($user, null); $params = $this->request()->getParams(); if (array_key_exists('xdmTarget', $params)) { unset($params['xdmTarget']); } if (array_key_exists('view', $params)) { unset($params['view']); } if (array_key_exists('xfToken', $params)) { unset($params['xfToken']); } if (array_key_exists('time', $params)) { unset($params['time']); } if (array_key_exists('xdm_e', $params)) { unset($params['xdm_e']); } if (array_key_exists('user', $params)) { unset($params['user']); } if (array_key_exists('xdm_c', $params)) { unset($params['xdm_c']); } if (array_key_exists('xdm_p', $params)) { unset($params['xdm_p']); } if (xapp_is_option(self::SIGNED_REQUEST_CALLBACK, $this)) { if (!(bool) call_user_func_array(xapp_get_option(self::SIGNED_REQUEST_CALLBACK, $this), array($this->request(), $params, $key))) { Xapp_Rpc_Fault::t("verifying signed request failed", array(1401510, -32010)); } } else { if ($key !== null) { if (isset($params[xapp_get_option(self::SIGNED_REQUEST_SIGN_PARAM, $this)])) { unset($params[xapp_get_option(self::SIGNED_REQUEST_SIGN_PARAM, $this)]); } if ($sign !== self::sign($params, $key)) { Xapp_Rpc_Fault::t("verifying signed request failed", array(1401510, -32010)); } } else { throw new Xapp_Rpc_Gateway_Exception(_("user identification key must be set when using internal signed request verification"), 1401511); } } } } break; default: } }
private function setupRPC() { /*** * We support JSONP for all services */ $isJSONP = false; $hasJSONP = true; if ($hasJSONP) { $isJSONP = XApp_Service_Entry_Utils::isJSONP(); } $method = $_SERVER['REQUEST_METHOD']; if ($method === 'POST') { $hasJSONP = false; } /*** * Filtered methods */ $ignoredRPCMethods = array('load', 'getObject', 'init', 'setup', 'log', 'onBeforeCall', 'onAfterCall', 'dumpObject', 'applyFilter', 'getLastJSONError', 'cleanUrl', 'rootUrl', 'siteUrl', 'getXCOption', 'getIndexer', 'getIndexOptions', 'getIndexOptions', 'indexDocument', 'onBeforeSearch', 'toDSURL', 'searchTest'); if (xapp_get_option(self::IGNORED_RPC_METHODS, $this)) { $ignoredRPCMethods = array_merge(xapp_get_option(self::IGNORED_RPC_METHODS, $this), $ignoredRPCMethods); } elseif (xapp_has_option(self::AUTH_DELEGATE, $this)) { /*** * Additional security here, mark each service method which has not been authorized by the * auth delegate as ignored! */ $authDelegate = xapp_get_option(self::AUTH_DELEGATE, $this); if (method_exists($authDelegate, 'authorize')) { $xCommanderFunctionTable = XApp_Service_Entry_Utils::getXCommanderFuncTable(); foreach ($xCommanderFunctionTable as $key => $value) { if (!$authDelegate::authorize($value)) { array_push($ignoredRPCMethods, $value); } } } } $server = null; if ($hasJSONP && $isJSONP) { //Options for SMD based JSONP-RPC classes $opt = array(Xapp_Rpc_Smd::IGNORE_METHODS => $ignoredRPCMethods, Xapp_Rpc_Smd::IGNORE_PREFIXES => array('_', '__')); $smd = new Xapp_Rpc_Smd_Jsonp($opt); //Options for RPC server $opt = array(Xapp_Rpc_Server::ALLOW_FUNCTIONS => true, Xapp_Rpc_Server::APPLICATION_ERROR => false, Xapp_Rpc_Server::METHOD_AS_SERVICE => true, Xapp_Rpc_Server::DEBUG => XApp_Service_Entry_Utils::isDebug(), Xapp_Rpc_Server::SMD => $smd); $server = Xapp_Rpc::server(XApp_Service_Entry_Utils::isRaw() ? 'raw' : 'jsonp', $opt); } else { //Options for SMD based RPC classes $opt = array(Xapp_Rpc_Smd_Json::IGNORE_METHODS => $ignoredRPCMethods, Xapp_Rpc_Smd_Json::IGNORE_PREFIXES => array('_', '__'), Xapp_Rpc_Smd_Json::METHOD_TARGET => false, Xapp_Rpc_Smd_Json::SERVICE_OVER_GET => true, Xapp_Rpc_Smd_Json::TARGET => xapp_get_option(self::RPC_TARGET, $this)); $smd = new Xapp_Rpc_Smd_Json($opt); //Options for RPC server $opt = array(Xapp_Rpc_Server::ALLOW_FUNCTIONS => true, Xapp_Rpc_Server::APPLICATION_ERROR => false, Xapp_Rpc_Server::METHOD_AS_SERVICE => false, Xapp_Rpc_Server::ALLOW_BATCHED_REQUESTS => true, Xapp_Rpc_Server::SERVICE_OVER_GET => true, Xapp_Rpc_Server::DEBUG => XApp_Service_Entry_Utils::isDebug(), Xapp_Rpc_Server::VALIDATE => !XApp_Service_Entry_Utils::isUpload(), Xapp_Rpc_Server::SMD => $smd); $server = Xapp_Rpc::server('json', $opt); } if ($server) { xapp_set_option(self::RPC_SERVER, $server, $this); } }
/** * invoke class/method or function via rpc or outside rpc functionality for testing purposes. the first parameter * thus can be a valid callable when used outside of rpc service or the internal array containing all call parameters * from concrete rpc server implementation. throws exception with extended exception properties if in debug mode. * summarizes all caught applications exceptions when invoking callable to a general application error so sensitive * error messages can be omitted. when invoked with named parameters will reorder parameters to reflect * order of method/function call since call_user_func_array needs to pass parameter named or unnamed in * correct order * * @error 14211 * @param array|callable $call expects either valid callable or array with all call parameters * @param array $params expects the parameters to pass to function/method to invoke * @return mixed * @throws Xapp_Rpc_Server_Exception * @throws Xapp_Rpc_Fault * @throws Exception */ public function invoke($call, $params = null) { $key = null; $hash = null; $class = null; $return = null; $result = null; $callable = null; try { //invoke from outside of rpc service if (is_callable($call)) { $callable = $call; if (is_array($call)) { $call = array(null, $call[1], is_object($call[0]) ? get_class($call[0]) : $call[0]); $class = new ReflectionClass($call[2]); } else { if (strpos((string) $call, '::') !== false) { $call = array(null, substr($call, strpos($call, '::') + 2), substr($call, 0, strpos($call, '::'))); $class = new ReflectionClass($call[2]); } else { $call = array(null, $call, $call); $class = null; } } } else { if (is_array($call)) { if ($call[2] !== null) { if (xapp_get_option(self::NAMESPACE_IDENTIFIER, $this) === NAMESPACE_SEPARATOR) { if (strpos($call[2], NAMESPACE_SEPARATOR) !== false) { $call[2] = NAMESPACE_SEPARATOR . str_replace(array('\\', '/', '_', '.'), xapp_get_option(self::NAMESPACE_IDENTIFIER, $this), trim($call[2], ' ' . NAMESPACE_SEPARATOR)); } else { $call[2] = str_replace(array('/', '_', '.'), xapp_get_option(self::NAMESPACE_IDENTIFIER, $this), trim($call[2])); } } else { $call[2] = str_replace(array('\\', '/', '_', '.'), xapp_get_option(self::NAMESPACE_IDENTIFIER, $this), trim($call[2])); } $key = "{$call[2]}.{$call[1]}"; try { if (array_key_exists(strtolower($call[2]), $this->_objects)) { $class = new ReflectionClass($this->_objects[strtolower($call[2])]); } else { $class = new ReflectionClass($call[2]); } if ($class->hasMethod($call[1])) { $method = $class->getMethod($call[1]); if ($method->isPublic()) { if ($method->isStatic()) { $callable = array($class->getName(), $call[1]); } else { $callable = array(array_key_exists(strtolower($call[2]), $this->_objects) ? $this->_objects[strtolower($call[2])] : $class->newInstance(), $call[1]); } } else { Xapp_Rpc_Fault::t("method: {$call[1]} of class: {$call[2]} is not public", array(1421105, -32601)); } } else { Xapp_Rpc_Fault::t("method: {$call[1]} of class: {$call[2]} does not exist", array(1421104, -32601)); } } catch (ReflectionException $e) { throw new Xapp_Rpc_Server_Exception(xapp_sprintf(_("unable to initialize class due to reflection error: %d, %s"), $e->getCode(), $e->getMessage()), 1421103); } } else { $key = $call[1]; $callable = $call[1]; } } else { throw new Xapp_Rpc_Server_Exception(_("invalid callable passed to invoke method"), 1421106); } } if (is_callable($callable)) { if (is_array($callable) && is_object($callable[0])) { $this->_class = $callable[0]; } if (!is_null($key) && !is_null($params) && array_values((array) $params) !== (array) $params) { $tmp = array(); $arr = (array) $params; $map = $this->smd()->get($key); if (!is_null($map)) { foreach ((array) $map->parameters as $p) { if (array_key_exists($p->name, $arr)) { $tmp[$p->name] = $arr[$p->name]; } } } $params = $tmp; $tmp = null; $arr = null; $map = null; } if (!is_null($key) && xapp_is_option(self::CACHE, $this) && preg_match(Xapp_Rpc::regex(xapp_get_option(self::CACHE_SERVICES, $this)), $call[0])) { if (xapp_get_option(self::CACHE_BY_TRANSACTION_ID, $this)) { $hash = $call[3]['id']; } else { $hash = Xapp_Cache::hash(serialize($call[0]) . serialize($params)); } if (xapp_get_option(self::CACHE, $this)->has($hash)) { return xapp_get_option(self::CACHE, $this)->get($hash); } } if (!xapp_get_option(self::PARAMS_AS_ARRAY, $this)) { $params = xapp_array_to_object($params); } xapp_event('xapp.rpc.server.beforeCall', array($this, array(&$result, $call[1], $call[2], $params))); if ($this->hasClass() && $class->implementsInterface('Xapp_Rpc_Interface_Callable')) { $return = $this->getClass()->onBeforeCall($this, array(&$result, $call[1], $call[2], &$params)); } if ($return !== false && $result === null) { $this->response()->result($result = call_user_func_array($callable, (array) $params)); } if ($return === false && $this->hasClass() && $class->implementsInterface('Xapp_Rpc_Interface_Callable')) { $return = $this->getClass()->onAbort($this, array(&$result, $call[1], $call[2], &$params)); } else { $return = null; } if ($return !== null) { $result = $return; } if ($this->hasClass() && $class->implementsInterface('Xapp_Rpc_Interface_Callable')) { $return = $this->getClass()->onAfterCall($this, array(&$result)); } if ($return !== null) { $result = $return; } xapp_event('xapp.rpc.server.afterCall', array($this, array(&$result))); if (!is_null($hash)) { if (!xapp_get_option(self::CACHE, $this)->has($hash)) { xapp_get_option(self::CACHE, $this)->set($hash, $result); } } $callable = null; $return = null; $params = null; $class = null; $hash = null; return $result; } else { throw new Xapp_Rpc_Server_Exception(_("unable to invoke function since first argument is not a callable"), 1421102); } } catch (Exception $e) { if ($this->hasClass() && $class->implementsInterface('Xapp_Rpc_Interface_Callable')) { $error = $this->getClass()->onError($this, $e); if ($error instanceof Exception) { $e = $error; $error = null; } } if (!$e instanceof Xapp_Rpc_Server_Exception) { $data = array(); $debug = xapp_get_option(self::DEBUG, $this); if (is_bool($debug) && $debug === true || is_int($debug) && $debug === 1) { $data['message'] = $e->getMessage(); $data['code'] = $e->getCode(); $data['class'] = get_class($e); $data['file'] = $e->getFile(); $data['line'] = $e->getLine(); if ($e instanceof ErrorException) { $data['severity'] = $e->getSeverity(); } if ($e instanceof Xapp_Rpc_Fault && $e->hasData()) { $data['data'] = $e->getData(); } } else { if (is_int($debug) && $debug === 2) { $data['message'] = $e->getMessage(); $data['code'] = $e->getCode(); if ($e instanceof ErrorException) { $data['severity'] = $e->getSeverity(); } if ($e instanceof Xapp_Rpc_Fault && $e->hasData()) { $data['data'] = $e->getData(); } } } if (xapp_is_option(self::APPLICATION_ERROR, $this)) { if ($e instanceof Xapp_Rpc_Fault && $e->hasFault()) { $f = $e->getFault(); } else { $f = -32500; } if (($code = (int) $e->getCode()) !== 0) { Xapp_Rpc_Fault::t(xapp_sprintf("application error: %d", array($code)), array(1421101, $f), XAPP_ERROR_IGNORE, $data); } else { Xapp_Rpc_Fault::t("application error", array(1421101, $f), XAPP_ERROR_IGNORE, $data); } } else { if ((bool) $debug) { Xapp_Rpc_Fault::t($e->getMessage(), $e->getCode(), XAPP_ERROR_IGNORE, $data); } else { throw $e; } } } else { throw $e; } } return null; }