Mainly used so we don't type constructors and function calls with 5-6 parameters...this makes it nicer and more practical :)
Example #1
0
 public function testGetCallbackResultException()
 {
     $methodData = ['method' => 'testCallbackException', 'cache' => ['ttl' => 0], 'header' => ['cache' => ['expires' => 0], 'status' => ['success' => 200, 'error' => 404, 'errorMessage' => '']]];
     $requestBag = new RequestBag();
     $requestBag->setApi('CacheTest')->setClassData(['class' => 'Webiny\\Component\\Rest\\Tests\\Mocks\\MockApiClassCallback', 'version' => '1.0'])->setMethodData($methodData)->setMethodParameters([]);
     $callback = new Callback($requestBag);
     $response = $callback->getCallbackResult()->getOutput();
     $this->assertSame('There has been an error processing the request.', $response['errorReport']['message']);
 }
Example #2
0
 /**
  * Checks if user is within rate limits.
  *
  * @param RequestBag     $requestBag
  * @param CallbackResult $cr
  *
  * @return bool
  * @throws \Webiny\Component\Rest\RestException
  */
 public static function isWithinRateLimits(RequestBag $requestBag, CallbackResult $cr)
 {
     // do we have rate control in place?
     if (!($rateControl = $requestBag->getApiConfig()->get('RateControl', false))) {
         return true;
         // if rate control is not set, user is within his limits
     }
     // check if we should ignore rate control for this particular method
     if (isset($requestBag->getMethodData()['rateControl']['ignore'])) {
         return true;
     }
     // verify that we have a Cache service set
     if (!($cache = $requestBag->getApiConfig()->get('Cache', false))) {
         throw new RestException('Rest Rate Control requires that you have a Cache service defined
         under the Rest configuration.');
     }
     // set the limit in response header
     $cr->attachDebugHeader('RateControl-Limit', $rateControl->Limit, true);
     // get current usage
     $cacheKey = md5('Webiny.Rest.RateLimit.' . self::httpRequest()->getClientIp());
     $cacheData = self::cache($cache)->read($cacheKey);
     if (!$cacheData) {
         $cacheData = ['usage' => 0, 'penalty' => 0, 'ttl' => time() + 60 * $rateControl->Interval];
     } else {
         $cacheData = self::unserialize($cacheData);
         // validate the ttl
         if (time() > $cacheData['ttl']) {
             $cacheData = ['usage' => 0, 'penalty' => 0, 'ttl' => time() + 60 * $rateControl->Interval];
         }
     }
     // check if user is already in penalty
     if ($cacheData['penalty'] > time()) {
         // when in penalty the reset value, equals the penalty value
         $cr->attachDebugHeader('RateControl-Reset', $cacheData['penalty'] - time(), true);
         // and the remaining equals 0
         $cr->attachDebugHeader('RateControl-Remaining', 0, true);
         return false;
     }
     // check if rate is reached
     if ($cacheData['usage'] >= $rateControl->Limit) {
         // set penalty for reaching the limit
         $cr->attachDebugHeader('RateControl-Reset', $rateControl->Penalty * 60 + time(), true);
         // and the remaining 0
         $cr->attachDebugHeader('RateControl-Remaining', 0, true);
         return false;
     }
     // if limit not reached, increment the usage and save the data
     $cacheData['usage']++;
     $cr->attachDebugHeader('RateControl-Remaining', $rateControl->Limit - $cacheData['usage'], true);
     $cr->attachDebugHeader('RateControl-Reset', $cacheData['ttl'], true);
     $cacheTtl = $rateControl->Interval > $rateControl->Penalty ? $rateControl->Interval : $rateControl->Penalty;
     self::cache($cache)->save($cacheKey, self::serialize($cacheData), $cacheTtl * 60);
     return true;
 }
Example #3
0
 public function testPurgeResult()
 {
     $requestBag = new RequestBag();
     // populate request bag and point the cache key creation to the mocked class
     $requestBag->setApi('CacheTest')->setMethodData(['cache' => ['ttl' => 100]])->setClassInstance(new MockCacheTestApiClass())->setClassData(['cacheKeyInterface' => true]);
     \Webiny\Component\Rest\Response\Cache::saveResult($requestBag, 'my result');
     $result = \Webiny\Component\Rest\Response\Cache::getFromCache($requestBag);
     $this->assertSame('my result', $result);
     \Webiny\Component\Rest\Response\Cache::purgeResult($requestBag);
     $result = \Webiny\Component\Rest\Response\Cache::getFromCache($requestBag);
     $this->assertFalse($result);
 }
Example #4
0
 /**
  * Checks if current user has access to the current rest request.
  *
  * @param RequestBag $requestBag
  *
  * @return bool
  * @throws \Webiny\Component\Rest\RestException
  */
 public static function hasAccess(RequestBag $requestBag)
 {
     // first we check if method requires a special access level
     if (!$requestBag->getApiConfig()->get('Security', false)) {
         return true;
         // no special access level required
     }
     // get the required role
     if (isset($requestBag->getMethodData()['role'])) {
         $role = $requestBag->getMethodData()['role'];
     } else {
         $role = $requestBag->getApiConfig()->get('Security.Role', 'ROLE_ANONYMOUS');
     }
     // check if user has the access level required
     if ($requestBag->getClassData()['accessInterface']) {
         return $requestBag->getClassInstance()->hasAccess($role);
     } else {
         // get firewall name
         $firewallName = $requestBag->getApiConfig()->get('Security.Firewall', false);
         if (!$firewallName) {
             throw new RestException('When using Rest access rule, you must specify a Firewall in your configuration.
             Alternatively you can implement the AccessInterface and do the check on your own.');
         }
         return self::security()->firewall($firewallName)->getUser()->hasRole($role);
     }
 }
Example #5
0
 /**
  * Computes the cache key, or gets it from the implemented interface from the api class.
  *
  * @return string Cache key.
  */
 private function getCacheKey()
 {
     if ($this->requestBag->getClassData()['cacheKeyInterface']) {
         $cacheKey = $this->requestBag->getClassInstance()->getCacheKey();
     } else {
         $url = $this->httpRequest()->getCurrentUrl(true);
         $cacheKey = 'path-' . $url->getPath();
         $cacheKey .= 'query-' . $this->serialize($url->getQuery());
         $cacheKey .= 'method-' . $this->httpRequest()->getRequestMethod();
         $cacheKey .= 'post-' . $this->serialize($this->httpRequest()->getPost()->getAll());
         $cacheKey .= 'payload-' . $this->serialize($this->httpRequest()->getPayload()->getAll());
         $cacheKey .= 'version-' . $this->requestBag->getClassData()['version'];
         $cacheKey = md5($cacheKey);
     }
     return $cacheKey;
 }
Example #6
0
 /**
  * Analyzes the request and tries to match an api method.
  *
  * @param array $classData Class array form compiled cache file.
  *
  * @return CallbackResult
  * @throws RestException
  * @throws \Exception
  */
 private function matchRequest(&$classData)
 {
     if (!is_array($classData)) {
         throw new RestException("Invalid class cache data.");
     }
     // build the request url upon which we will do the matching
     try {
         $url = $this->getUrl();
     } catch (\Exception $e) {
         throw $e;
     }
     // get request method
     $method = $this->getMethod();
     if (!in_array($method, self::$supportedRequestTypes)) {
         throw new RestException('Unsupported request method: "' . $method . '"');
     }
     $callbacks = empty($classData[$method]) ? [] : $classData[$method];
     // match array
     $matchedMethod = ['methodData' => false, 'matchedParameters' => false];
     // validate that we have the ending class name in the url
     $classUrl = PathTransformations::classNameToUrl($this->class, $this->normalize);
     if (strpos($url, '/' . $classUrl . '/') !== false) {
         $matchedMethod = $this->matchMethod($callbacks, $url);
         // if method was not matched
         if (!$matchedMethod['methodData']) {
             // if no method was matched, let's try to match a default method
             $matchedMethod = $this->matchDefaultMethod($callbacks, $url, $classUrl);
         }
     }
     $methodData = isset($matchedMethod['methodData']) ? $matchedMethod['methodData'] : false;
     $matchedParameters = $matchedMethod['matchedParameters'] ? $matchedMethod['matchedParameters'] : [];
     $requestBag = new RequestBag();
     $requestBag->setClassData($classData)->setMethodData($methodData)->setMethodParameters($matchedParameters)->setApi($this->api);
     $callback = new Callback($requestBag);
     return $callback->getCallbackResult();
 }
Example #7
0
 public function testSetClassInstance()
 {
     $rb = new RequestBag();
     $rb->setClassInstance('instance');
     $this->assertSame('instance', $rb->getClassInstance());
 }
Example #8
0
 /**
  * Processes the callback and returns an instance of CallbackResult.
  *
  * @return CallbackResult
  * @throws \Webiny\Component\Rest\RestException
  */
 public function getCallbackResult()
 {
     $class = $this->requestBag->getClassData()['class'];
     $this->requestBag->setClassInstance(new $class());
     // create CallbackResult instance
     $cr = new CallbackResult();
     $env = 'production';
     if ($this->requestBag->getApiConfig()->get('Environment', 'production') == 'development') {
         $cr->setEnvToDevelopment();
         $env = 'development';
     }
     // attach some metadata
     $cr->attachDebugHeader('Class', $class);
     $cr->attachDebugHeader('ClassVersion', $this->requestBag->getClassData()['version']);
     $cr->attachDebugHeader('Method', strtoupper($this->httpRequest()->getRequestMethod()));
     if (!$this->requestBag->getMethodData()) {
         // if no method matched the request
         $cr->setHeaderResponse(404);
         $cr->setErrorResponse('No service matched the request.');
         return $cr;
     }
     // check rate limit
     try {
         $rateControl = RateControl::isWithinRateLimits($this->requestBag, $cr);
         if (!$rateControl) {
             $cr->setHeaderResponse(429);
             $cr->setErrorResponse('Rate control limit reached.');
             return $cr;
         }
     } catch (\Exception $e) {
         throw new RestException('Rate control verification failed. ' . $e->getMessage());
     }
     // verify access role
     try {
         $hasAccess = Security::hasAccess($this->requestBag);
         if (!$hasAccess) {
             $cr->setHeaderResponse(403);
             $cr->setErrorResponse('You don\'t have the required access level.');
             $cr->attachDebugHeader('RequestedRole', $this->requestBag->getMethodData()['role']);
             return $cr;
         }
     } catch (\Exception $e) {
         throw new RestException('Access role verification failed. ' . $e->getMessage());
     }
     // verify cache
     try {
         $cachedResult = Cache::getFromCache($this->requestBag);
     } catch (\Exception $e) {
         throw new RestException('Reading result from cache failed. ' . $e->getMessage());
     }
     // finalize output
     if ($cachedResult) {
         $cr->setData($cachedResult);
         $cr->attachDebugHeader('Cache', 'HIT');
     } else {
         try {
             $result = call_user_func_array([$this->requestBag->getClassInstance(), $this->requestBag->getMethodData()['method']], $this->requestBag->getMethodParameters());
             // check if method has custom headers set
             $cr->setHeaderResponse($this->requestBag->getMethodData()['header']['status']['success']);
             $cr->attachDebugHeader('Cache', 'MISS');
             // add result to output
             $cr->setData($result);
             // check if we need to attach the cache headers
             if ($this->requestBag->getMethodData()['header']['cache']['expires'] > 0) {
                 $cr->setExpiresIn($this->requestBag->getMethodData()['header']['cache']['expires']);
             }
             // cache the result
             Cache::saveResult($this->requestBag, $result);
         } catch (RestErrorException $re) {
             // check if method has custom headers set
             $cr->setHeaderResponse($statusCode = $re->getResponseCode(), $this->requestBag->getMethodData()['header']['status']['errorMessage']);
             // check if a custom http response code is set
             $cr->setErrorResponse($re->getErrorMessage(), $re->getErrorDescription(), $re->getErrorCode());
             $errors = $re->getErrors();
             foreach ($errors as $e) {
                 $cr->addErrorMessage($e);
             }
             if ($env == 'development') {
                 $cr->addDebugMessage(['file' => $re->getFile(), 'line' => $re->getLine(), 'traces' => explode('#', $re->getTraceAsString())]);
             }
         } catch (\Exception $e) {
             // check if method has custom headers set
             $cr->setHeaderResponse($this->requestBag->getMethodData()['header']['status']['error'], $this->requestBag->getMethodData()['header']['status']['errorMessage']);
             $cr->setErrorResponse('There has been an error processing the request.');
             if ($env == 'development') {
                 $cr->addErrorMessage(['message' => $e->getMessage()]);
                 $cr->addDebugMessage(['file' => $e->getFile(), 'line' => $e->getLine(), 'traces' => explode('#', $e->getTraceAsString())]);
             }
         }
     }
     return $cr;
 }