Implement select query validation and dynamic subquery filtering for users

Co-authored-by: jakeb994 <jakeb994@gmail.com>
This commit is contained in:
Cursor Agent
2025-08-12 12:11:49 +00:00
co-authored by jakeb994
parent f81fd16d26
commit f5d39f8a95
3 changed files with 109 additions and 1 deletions
+49 -1
View File
@@ -643,6 +643,54 @@ App::get('/v1/users')
$cursor->setValue($cursorDocument);
}
// Define attributes that have subQueryX filters
$subQueryAttributes = [
'authenticators' => 'subQueryAuthenticators',
'sessions' => 'subQuerySessions',
'tokens' => 'subQueryTokens',
'challenges' => 'subQueryChallenges',
'memberships' => 'subQueryMemberships',
'targets' => 'subQueryTargets'
];
// Process select queries and identify subQueryX attributes
$skipFilters = ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships', 'subQueryTargets'];
$additionalSkipFilters = [];
// Process queries to handle select queries with subQueryX attributes
$processedQueries = [];
foreach ($queries as $query) {
if ($query->getMethod() === Query::TYPE_SELECT) {
$selectedAttributes = $query->getValues();
$filteredAttributes = [];
foreach ($selectedAttributes as $attribute) {
if (array_key_exists($attribute, $subQueryAttributes)) {
// Add the corresponding subQuery filter to skipFilters
$additionalSkipFilters[] = $subQueryAttributes[$attribute];
} else {
// Keep this attribute in the select query
$filteredAttributes[] = $attribute;
}
}
// Only add the select query if there are valid attributes remaining
if (!empty($filteredAttributes)) {
$processedQueries[] = Query::select($filteredAttributes);
}
} else {
// Keep non-select queries as they are
$processedQueries[] = $query;
}
}
// Update queries with processed queries
$queries = $processedQueries;
// Add additional skip filters to the existing ones
$skipFilters = array_merge($skipFilters, $additionalSkipFilters);
$skipFilters = array_unique($skipFilters);
$users = [];
$total = 0;
@@ -655,7 +703,7 @@ App::get('/v1/users')
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
}, ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships']);
}, $skipFilters);
$response->dynamic(new Document([
'users' => $users,
@@ -11,6 +11,7 @@ use Utopia\Database\Validator\Query\Filter;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Query\Order;
use Appwrite\Utopia\Database\Validator\Query\Select;
class Base extends Queries
{
@@ -82,6 +83,7 @@ class Base extends Queries
new Cursor(),
new Filter($attributes, APP_DATABASE_QUERY_MAX_VALUES),
new Order($attributes),
new Select($attributes),
];
parent::__construct($validators);
@@ -0,0 +1,58 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Query;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query;
class Select extends Query
{
protected array $schema = [];
/**
* @param Document[] $attributes
*/
public function __construct(array $attributes)
{
foreach ($attributes as $attribute) {
$this->schema[$attribute->getAttribute('key')] = $attribute->getAttribute('type');
}
}
protected function isValidAttribute(string $attribute): bool
{
if (str_starts_with($attribute, '$')) {
return true; // Allow system attributes
}
return array_key_exists($attribute, $this->schema);
}
public function isValid(mixed $query): bool
{
if (!$query instanceof \Utopia\Database\Query) {
return false;
}
if ($query->getMethod() !== Query::TYPE_SELECT) {
return false;
}
$values = $query->getValues();
foreach ($values as $attribute) {
if (!$this->isValidAttribute($attribute)) {
$this->message = 'Query select is not valid: Attribute "' . $attribute . '" not found.';
return false;
}
}
return true;
}
public function getType(): string
{
return Query::TYPE_SELECT;
}
}