From e109190d3fbf517076eaad2940071d1ed73a5619 Mon Sep 17 00:00:00 2001 From: codyllord Date: Tue, 23 Sep 2025 22:50:50 +0000 Subject: [PATCH 1/2] add functionality to filter services seen by non-admin users based on their assigned roles and permissions --- src/Models/Service.php | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/Models/Service.php b/src/Models/Service.php index ca0773cd..c40b0809 100644 --- a/src/Models/Service.php +++ b/src/Models/Service.php @@ -8,6 +8,7 @@ use DreamFactory\Core\Events\ServiceDeletedEvent; use DreamFactory\Core\Events\ServiceModifiedEvent; use DreamFactory\Core\Exceptions\BadRequestException; +use DreamFactory\Core\Utility\Session; use Illuminate\Database\Query\Builder; use Illuminate\Support\Arr; use ServiceManager; @@ -280,4 +281,90 @@ protected static function cleanResult($response, $fields) return $response; } + + /** + * Override selectByRequest to apply RBAC filtering for API docs + * + * @param array $criteria + * @param array $options + * @return mixed + */ + public static function selectByRequest(array $criteria = [], array $options = []) + { + $condition = array_get($criteria, 'condition', ''); + $params = array_get($criteria, 'params', []); + + // Detect different types of service listing requests that should be filtered by RBAC + + // 1. API Docs Request: filtering for non-swagger services + $isApiDocsRequest = ( + (strpos(strtolower($condition), 'type') !== false && strpos(strtolower($condition), 'not like') !== false) || + (strpos($condition, '`type`') !== false && strpos($condition, 'NOT LIKE') !== false) + ) && ( + !empty($params) && is_array($params) && + count(array_filter($params, function($p) { return strpos($p, 'swagger') !== false; })) > 0 + ); + + // 2. Services Management Request: general service listing (no specific type filter or type IN filter) + $isServicesManagementRequest = ( + empty($condition) || // No filter condition + (strpos($condition, 'type in') !== false) || // Type IN filter (service type filtering) + (strpos($condition, '`type` IN') !== false) // SQL formatted type IN filter + ); + + // 3. Database Services Request: filtering for database-related service types + $isDatabaseServicesRequest = !empty($params) && is_array($params) && + count(array_filter($params, function($p) { + return in_array($p, ['mysql', 'sqlite', 'postgres', 'sqlserver', 'mongodb', 'cassandra', 'oracle', 'firebird']); + })) > 0; + + // 4. Role Configuration Request: when configuring service access for roles + // This can be detected by checking if we're in a role management context + $isRoleConfigRequest = strpos($condition, 'is_active') !== false && empty($params); + + // Apply RBAC filtering for non-admin users on any of these request types + $shouldApplyFiltering = ($isApiDocsRequest || $isServicesManagementRequest || $isDatabaseServicesRequest || $isRoleConfigRequest); + + if ($shouldApplyFiltering && !Session::isSysAdmin()) { + // Get the current user's accessible services + $userServices = static::getUserAccessibleServices(); + + if (empty($userServices)) { + // User has no service access, return empty result + return []; + } + + // Add service ID filter to criteria + $serviceIds = implode(',', $userServices); + $existingCondition = array_get($criteria, 'condition', ''); + + if (!empty($existingCondition)) { + $criteria['condition'] = $existingCondition . " and id in ($serviceIds)"; + } else { + $criteria['condition'] = "id in ($serviceIds)"; + } + } + + return parent::selectByRequest($criteria, $options); + } + + /** + * Get service IDs that the current user has access to based on their role + * + * @return array + */ + protected static function getUserAccessibleServices() + { + $roleServices = (array)Session::get('role.services'); + $accessibleServiceIds = []; + + foreach ($roleServices as $serviceAccess) { + $serviceId = array_get($serviceAccess, 'service_id'); + if (!empty($serviceId) && !in_array($serviceId, $accessibleServiceIds)) { + $accessibleServiceIds[] = $serviceId; + } + } + + return $accessibleServiceIds; + } } From 1bbae747aa0385ed8b8956f03c336bd8ac702e21 Mon Sep 17 00:00:00 2001 From: nicdavidson Date: Thu, 25 Sep 2025 16:25:29 +0000 Subject: [PATCH 2/2] fix: Resolve critical SQL injection vulnerability in RBAC service filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace string concatenation with parameterized queries in Service::selectByRequest() - Add input validation for service IDs (numeric check and type casting) - Prevent SQL injection attacks in role-based access control filtering - Maintain backward compatibility while securing database queries 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/Models/Service.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Models/Service.php b/src/Models/Service.php index c40b0809..b253caf7 100644 --- a/src/Models/Service.php +++ b/src/Models/Service.php @@ -334,15 +334,19 @@ public static function selectByRequest(array $criteria = [], array $options = [] return []; } - // Add service ID filter to criteria - $serviceIds = implode(',', $userServices); + // Add service ID filter to criteria using parameterized queries + $placeholders = str_repeat('?,', count($userServices) - 1) . '?'; $existingCondition = array_get($criteria, 'condition', ''); + $existingParams = array_get($criteria, 'params', []); if (!empty($existingCondition)) { - $criteria['condition'] = $existingCondition . " and id in ($serviceIds)"; + $criteria['condition'] = $existingCondition . " and id in ($placeholders)"; } else { - $criteria['condition'] = "id in ($serviceIds)"; + $criteria['condition'] = "id in ($placeholders)"; } + + // Merge the service IDs with existing parameters + $criteria['params'] = array_merge($existingParams, $userServices); } return parent::selectByRequest($criteria, $options); @@ -360,8 +364,9 @@ protected static function getUserAccessibleServices() foreach ($roleServices as $serviceAccess) { $serviceId = array_get($serviceAccess, 'service_id'); - if (!empty($serviceId) && !in_array($serviceId, $accessibleServiceIds)) { - $accessibleServiceIds[] = $serviceId; + // Validate that service_id is a positive integer + if (!empty($serviceId) && is_numeric($serviceId) && $serviceId > 0 && !in_array($serviceId, $accessibleServiceIds)) { + $accessibleServiceIds[] = (int)$serviceId; } }