/** * Checks to see if there is a valid logged in user. * @throws \DreamFactory\Core\Exceptions\UnauthorizedException */ private static function checkUser() { $userId = SessionUtility::getCurrentUserId(); if (empty($userId)) { throw new UnauthorizedException('There is no valid session for the current request.'); } }
/** * Builds the selection criteria from request and returns it. * * @return array */ protected function getSelectionCriteria() { /** @type TableSchema $schema */ $schema = $this->getModel()->getTableSchema(); $criteria = ['params' => []]; if (null !== ($value = $this->request->getParameter(ApiOptions::FIELDS))) { $criteria['select'] = explode(',', $value); } else { $criteria['select'] = ['*']; } if (null !== ($value = $this->request->getPayloadData(ApiOptions::PARAMS))) { $criteria['params'] = $value; } if (null !== ($value = $this->request->getParameter(ApiOptions::FILTER))) { $native = $this->convertFilterToNative($value, $criteria['params'], [], $schema->columns); $criteria['condition'] = $native['where']; if (is_array($native['params'])) { if (is_array($criteria['params'])) { $criteria['params'] = array_merge($criteria['params'], $native['params']); } else { $criteria['params'] = $native['params']; } } // Add current user ID into parameter array if in condition, but not specified. if (false !== stripos($value, ':user_id')) { if (!isset($criteria['params'][':user_id'])) { $criteria['params'][':user_id'] = SessionUtility::getCurrentUserId(); } } } $value = intval($this->request->getParameter(ApiOptions::LIMIT)); $maxAllowed = $this->getMaxRecordsReturnedLimit(); if ($value < 1 || $value > $maxAllowed) { // impose a limit to protect server $value = $maxAllowed; } $criteria['limit'] = $value; // merge in possible payload options $optionNames = [ApiOptions::OFFSET, ApiOptions::ORDER, ApiOptions::GROUP]; foreach ($optionNames as $option) { if (null !== ($value = $this->request->getParameter($option))) { $criteria[$option] = $value; } elseif (!empty($otherNames = ApiOptions::getAliases($option))) { foreach ($otherNames as $other) { if (null !== ($value = $this->request->getParameter($other))) { $criteria[$option] = $value; } elseif (null !== ($value = $this->request->getPayloadData($other))) { $criteria[$option] = $value; } } } if (!isset($criteria[$option]) && null !== ($value = $this->request->getPayloadData($option))) { $criteria[$option] = $value; } } return $criteria; }
/** * @param $id * @param $record * @param array $params * * @return array * @throws \DreamFactory\Core\Exceptions\BadRequestException * @throws \DreamFactory\Core\Exceptions\InternalServerErrorException * @throws \DreamFactory\Core\Exceptions\NotFoundException */ public static function deleteInternal($id, $record, $params = []) { if (empty($record)) { throw new BadRequestException('There are no fields in the record to create . '); } if (empty($id)) { //Todo:perform logging below //Log::error( 'Update request with no id supplied: ' . print_r( $record, true ) ); throw new BadRequestException('Identifying field "id" can not be empty for update request . '); } $userId = SessionUtility::getCurrentUserId(); $model = static::whereUserId($userId)->whereName($id)->first(); if (!$model instanceof Model) { throw new NotFoundException('No resource found for ' . $id); } try { $result = static::buildResult($model, $params); $model->delete(); return $result; } catch (\Exception $ex) { throw new InternalServerErrorException('Failed to delete resource: ' . $ex->getMessage()); } }
/** * {@inheritdoc} */ public static function deleteInternal($id, $record, $params = []) { if (empty($record)) { throw new BadRequestException('There are no fields in the record to create . '); } if (empty($id)) { //Todo:perform logging below //Log::error( 'Update request with no id supplied: ' . print_r( $record, true ) ); throw new BadRequestException('Identifying field "id" can not be empty for update request . '); } /** @type User $model */ $model = static::find($id); if (!$model instanceof Model) { throw new NotFoundException('No resource found for ' . $id); } try { if ($model->is_sys_admin && !ArrayUtils::getBool($params, 'admin')) { throw new ForbiddenException('Not allowed to delete an admin user.'); } elseif (ArrayUtils::getBool($params, 'admin') && !$model->is_sys_admin) { throw new BadRequestException('Cannot delete a non-admin user.'); } elseif (Session::getCurrentUserId() === $model->id) { throw new ForbiddenException('Cannot delete your account.'); } $result = static::buildResult($model, $params); $model->delete(); return $result; } catch (\Exception $ex) { if (!$ex instanceof ForbiddenException && !$ex instanceof BadRequestException) { throw new InternalServerErrorException('Failed to delete resource: ' . $ex->getMessage()); } else { throw $ex; } } }
/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { //Get the Console API Key $consoleApiKey = AccessCheck::getConsoleApiKey($request); // Get limits if (config('df.standalone') === true || $consoleApiKey === Managed::getConsoleKey()) { return $next($request); } else { $limits = Managed::getLimits(); // The limits array comes across from the console as a bunch of Std Objects, need to turn it back // into an array $limits['api'] = (array) $limits['api']; foreach (array_keys($limits['api']) as $key) { $limits['api'][$key] = (array) $limits['api'][$key]; } } if (!empty($limits) && is_null($this->_getServiceName()) === false) { $this->_inUnitTest = \Config::get('api_limits_test'); $userName = $this->_getUser(Session::getCurrentUserId()); $userRole = $this->_getRole(Session::getRoleId()); $apiName = $this->_getApiKey(Session::getApiKey()); $serviceName = $this->_getServiceName(); $clusterName = Managed::getClusterName(); // Build the list of API Hits to check $apiKeysToCheck = ['cluster.default' => 0, 'instance.default' => 0]; $serviceKeys[$serviceName] = 0; if (is_null($userRole) === false) { $serviceKeys[$serviceName . '.' . $userRole] = 0; } if (is_null($userName) === false) { $serviceKeys[$serviceName . '.' . $userName] = 0; } if (is_null($apiName) === false) { $apiKeysToCheck[$apiName] = 0; if (is_null($userRole) === false) { $apiKeysToCheck[$apiName . '.' . $userRole] = 0; } if (is_null($userName) === false) { $apiKeysToCheck[$apiName . '.' . $userName] = 0; } foreach ($serviceKeys as $key => $value) { $apiKeysToCheck[$apiName . '.' . $key] = $value; } } if (is_null($clusterName) === false) { $apiKeysToCheck[$clusterName] = 0; if (is_null($userRole) === false) { $apiKeysToCheck[$clusterName . '.' . $userRole] = 0; } if (is_null($userName) === false) { $apiKeysToCheck[$clusterName . '.' . $userName] = 0; } foreach ($serviceKeys as $key => $value) { $apiKeysToCheck[$clusterName . '.' . $key] = $value; } } if (is_null($userName) === false) { $apiKeysToCheck[$userName] = 0; } if (is_null($userRole) === false) { $apiKeysToCheck[$userRole] = 0; } $apiKeysToCheck = array_merge($apiKeysToCheck, $serviceKeys); $timePeriods = ['minute', 'hour', 'day', '7-day', '30-day']; $overLimit = false; try { foreach (array_keys($apiKeysToCheck) as $key) { foreach ($timePeriods as $period) { $keyToCheck = $key . '.' . $period; if (array_key_exists($keyToCheck, $limits['api']) === true) { $cacheValue = \Cache::get($keyToCheck, 0); $cacheValue++; \Cache::put($keyToCheck, $cacheValue, $limits['api'][$keyToCheck]['period']); if ($cacheValue > $limits['api'][$keyToCheck]['limit']) { $overLimit = true; } } } } } catch (\Exception $e) { return ResponseFactory::getException(new InternalServerErrorException('Unable to update cache'), $request); } if ($overLimit === true) { return ResponseFactory::getException(new TooManyRequestsException('Specified connection limit exceeded'), $request); } } return $next($request); }
/** * @param array $record * @param ColumnSchema[] $fields_info * @param array $filter_info * @param bool $for_update * @param array $old_record * * @return array * @throws \Exception */ protected function parseRecord($record, $fields_info, $filter_info = null, $for_update = false, $old_record = null) { $record = $this->interpretRecordValues($record); $parsed = empty($fields_info) ? $record : []; if (!empty($fields_info)) { $record = array_change_key_case($record, CASE_LOWER); foreach ($fields_info as $fieldInfo) { // add or override for specific fields switch ($fieldInfo->type) { case 'timestamp_on_create': if (!$for_update) { $parsed[$fieldInfo->name] = $this->getCurrentTimestamp(); } break; case 'timestamp_on_update': $parsed[$fieldInfo->name] = $this->getCurrentTimestamp(); break; case 'user_id_on_create': if (!$for_update) { $userId = Session::getCurrentUserId(); if (isset($userId)) { $parsed[$fieldInfo->name] = $userId; } } break; case 'user_id_on_update': $userId = Session::getCurrentUserId(); if (isset($userId)) { $parsed[$fieldInfo->name] = $userId; } break; default: $name = strtolower($fieldInfo->getName(true)); if (array_key_exists($name, $record)) { $fieldVal = ArrayUtils::get($record, $name); // due to conversion from XML to array, null or empty xml elements have the array value of an empty array if (is_array($fieldVal) && empty($fieldVal)) { $fieldVal = null; } // overwrite some undercover fields if ($fieldInfo->autoIncrement) { // should I error this? // drop for now unset($record[$name]); continue; } if (is_null($fieldVal) && !$fieldInfo->allowNull) { throw new BadRequestException("Field '{$name}' can not be NULL."); } /** validations **/ if (!static::validateFieldValue($fieldInfo->getName(true), $fieldVal, $fieldInfo->validation, $for_update, $fieldInfo)) { // if invalid and exception not thrown, drop it unset($record[$name]); continue; } $fieldVal = $this->parseValueForSet($fieldVal, $fieldInfo); $parsed[$fieldInfo->name] = $fieldVal; unset($record[$name]); } else { // if field is required, kick back error if ($fieldInfo->getRequired() && !$for_update) { throw new BadRequestException("Required field '{$name}' can not be NULL."); } break; } break; } } } if (!empty($filter_info)) { $this->validateRecord($parsed, $filter_info, $for_update, $old_record); } return $parsed; }
/** * Update the model in the database. * * @param array $attributes * * @return bool|int * @throws \Exception */ public function update(array $attributes = []) { $relations = []; $transaction = false; foreach ($attributes as $key => $value) { if ($this->isRelationMapped($key)) { $relations[$key] = $value; unset($attributes[$key]); } } if (count($relations) > 0) { DB::beginTransaction(); $transaction = true; } try { $userId = SessionUtility::getCurrentUserId(); if ($userId && static::isField('last_modified_by_id')) { $this->last_modified_by_id = $userId; } $updated = parent::update($attributes); if ($updated && $this->exists && count($relations) > 0) { foreach ($relations as $name => $value) { $relatedModel = $this->getReferencingModel($name); if (RelationSchema::HAS_MANY === $this->getReferencingType($name)) { $hasMany = $this->getHasManyByRelationName($name); $this->saveHasManyData($relatedModel, $hasMany, $value, $name); } } } if ($transaction) { DB::commit(); } } catch (\Exception $e) { if ($transaction) { DB::rollBack(); } throw $e; } return $updated; }
/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle(Request $request, Closure $next) { /** * It is assumed, if you get this far, that ClusterServiceProvider was registered via * the ManagedInstance bootstrapper. If not, you're in a world of shit. * * We use provider's service() method because Facades are not loaded yet */ $_cluster = ClusterServiceProvider::service(); // Get limits or bail if (!$_cluster instanceof ProvidesManagedLimits || empty($limits = $_cluster->getLimits())) { return $next($request); } $this->testing = config('api_limits_test', 'testing' == env('APP_ENV')); if (!empty($limits)) { $userName = $this->getUser(Session::getCurrentUserId()); $userRole = $this->getRole(Session::getRoleId()); $apiName = $this->getApiKey(Session::getApiKey()); $clusterName = $_cluster->getClusterId(); $instanceName = $_cluster->getInstanceName(); $serviceName = $this->getServiceName($request); $limits = json_encode($limits); //TODO: Update dfe-console to properly set this, but right now, we want to touch as few files as possible if (!$this->testing) { $limits = str_replace(['cluster.default', 'instance.default'], [$clusterName, $clusterName . '.' . $instanceName], $limits); } // Convert to an array $limits = json_decode($limits, true); // Build the list of API Hits to check $apiKeysToCheck = [$clusterName . '.' . $instanceName => 0]; $serviceKeys = []; if ($serviceName) { $serviceKeys[$serviceName] = 0; $userRole && ($serviceKeys[$serviceName . '.' . $userRole] = 0); $userName && ($serviceKeys[$serviceName . '.' . $userName] = 0); } if ($apiName) { $apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $apiName] = 0; $userRole && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $apiName . '.' . $userRole] = 0); $userName && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $apiName . '.' . $userName] = 0); foreach ($serviceKeys as $key => $value) { $apiKeysToCheck[$apiName . '.' . $key] = $value; } } if ($clusterName) { $apiKeysToCheck[$clusterName] = 0; $userRole && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $userRole] = 0); $userName && ($apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $userName] = 0); foreach ($serviceKeys as $key => $value) { $apiKeysToCheck[$clusterName . '.' . $instanceName . '.' . $key] = $value; } } /* Per Ben, we want to increment every limit they hit, not stop after the first one */ $overLimit = []; try { foreach (array_keys($apiKeysToCheck) as $key) { foreach ($this->periods as $period) { $_checkKey = $key . '.' . $period; /** @noinspection PhpUndefinedMethodInspection */ if (array_key_exists($_checkKey, $limits['api'])) { $_limit = $limits['api'][$_checkKey]; // For any cache drivers that make use of the cache prefix, we need to make sure we use // a prefix that every instance can see. But first, grab the current value $dfCachePrefix = env('DF_CACHE_PREFIX'); putenv('DF_CACHE_PREFIX' . '=' . 'df_limits'); $_ENV['DF_CACHE_PREFIX'] = $_SERVER['DF_CACHE_PREFIX'] = 'df_limits'; // Increment counter $cacheValue = $this->cache()->get($_checkKey, 0); $cacheValue++; if ($cacheValue > $_limit['limit']) { // Push the name of the rule onto the over-limit array so we can give the name in the 429 error message $overLimit[] = array_get($_limit, 'name', $_checkKey); } else { // Only increment the counter if we are not over the limit. Fixes DFE-205 $this->cache()->put($_checkKey, $cacheValue, $_limit['period']); } // And now set it back putenv('DF_CACHE_PREFIX' . '=' . $dfCachePrefix); $_ENV['DF_CACHE_PREFIX'] = $_SERVER['DF_CACHE_PREFIX'] = $dfCachePrefix; } } } } catch (\Exception $_ex) { return ResponseFactory::getException(new InternalServerErrorException('Unable to update cache: ' . $_ex->getMessage()), $request); } if ($overLimit) { /* Per Ben, we want to increment every limit they hit, not stop after the first one */ return ResponseFactory::getException(new TooManyRequestsException('API limit(s) exceeded: ' . implode(', ', $overLimit)), $request); } } return $next($request); }