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;
    }