Skip to content

Commit 16d9dbc

Browse files
authored
Merge pull request #143 from dreamfactorysoftware/develop
Develop
2 parents b3c53f5 + 4b97cc4 commit 16d9dbc

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

src/Http/Middleware/AccessCheck.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,18 @@ public function handle($request, Closure $next)
6464

6565
return $next($request);
6666
} catch (RestException $e) {
67-
68-
$permException = $e;
67+
// Try schema access fallback for GET requests to _schema
68+
if ($method === Verbs::GET && $component === '_schema') {
69+
\Log::info('Attempting schema access fallback for _schema request');
70+
$fallbackResponse = $this->handleSchemaAccessFallback($service, $method, $component, $requestor);
71+
if ($fallbackResponse !== null) {
72+
return $next($request);
73+
} else {
74+
$permException = $e;
75+
}
76+
} else {
77+
$permException = $e;
78+
}
6979
}
7080

7181
// No access allowed, figure out the best error response
@@ -115,4 +125,29 @@ private function isOAuthService($service)
115125
{
116126
return str_ends_with($service, '_oauth');
117127
}
128+
129+
/**
130+
* Handle schema access fallback for GET requests to _schema endpoints
131+
*
132+
* @param string $service Service name
133+
* @param string $method HTTP method
134+
* @param string $component Component name
135+
* @param int $requestor Requestor type
136+
* @return mixed Response or null if fallback not possible
137+
*/
138+
private function handleSchemaAccessFallback($service, $method, $component, $requestor)
139+
{
140+
// Only handle GET requests to _schema endpoints
141+
if ($method !== Verbs::GET || $component !== '_schema') {
142+
return null;
143+
}
144+
145+
$allowedComponents = Session::getAllowedComponentsForGet($service, $requestor);
146+
147+
if (empty($allowedComponents)) {
148+
return null;
149+
}
150+
151+
return $allowedComponents;
152+
}
118153
}

src/Services/ServiceManager.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,10 @@ public function handleRequest(
525525
if ($check_permission === true) {
526526
if (false === Session::checkServicePermission($verb, $service, $resource, Session::getRequestor(),
527527
$throw_exception)) {
528+
// For schema requests, try to get filtered results
529+
if ($verb === Verbs::GET && $resource === '_schema') {
530+
return $this->getFilteredSchemaResponse($service, $query);
531+
}
528532
return new ServiceResponse([]);
529533
}
530534
}
@@ -547,6 +551,141 @@ public function handleRequest(
547551
return $this->getService($service)->handleRequest($request, $resource);
548552
}
549553

554+
/**
555+
* Get filtered schema response based on user permissions
556+
*
557+
* @param string $service Service name
558+
* @param array $query Query parameters
559+
* @return ServiceResponse Filtered schema response
560+
*/
561+
private function getFilteredSchemaResponse($service, $query)
562+
{
563+
try {
564+
$serviceInstance = $this->getService($service);
565+
566+
// Create a request with as_list parameter
567+
$request = new ServiceRequest();
568+
$request->setMethod(Verbs::GET);
569+
$request->setParameters(array_merge($query, ['as_list' => true]));
570+
571+
// Handle the request without permission checks
572+
$response = $serviceInstance->handleRequest($request, '_schema');
573+
574+
// Filter the response based on user permissions
575+
$content = $response->getContent();
576+
$allowedComponents = Session::getAllowedComponentsForGet($service);
577+
578+
if (!empty($allowedComponents) && !in_array('*', $allowedComponents)) {
579+
$content = $this->filterSchemaContent($content, $allowedComponents);
580+
$response->setContent($content);
581+
}
582+
583+
return $response;
584+
585+
} catch (\Exception $e) {
586+
return new ServiceResponse([], 'application/json', 500);
587+
}
588+
}
589+
590+
/**
591+
* Filter schema content based on allowed components
592+
*
593+
* @param mixed $content Schema content to filter
594+
* @param array $allowedComponents Array of allowed component patterns
595+
* @return mixed Filtered content
596+
*/
597+
private function filterSchemaContent($content, $allowedComponents)
598+
{
599+
// Handle different content structures
600+
if (is_array($content)) {
601+
// Check if content is wrapped in resources wrapper
602+
$wrapper = config('df.resources_wrapper', 'resource');
603+
if (isset($content[$wrapper]) && is_array($content[$wrapper])) {
604+
$resources = $content[$wrapper];
605+
$filteredResources = $this->filterResourcesByAccess($resources, $allowedComponents);
606+
$content[$wrapper] = $filteredResources;
607+
return $content;
608+
} else {
609+
return $this->filterResourcesByAccess($content, $allowedComponents);
610+
}
611+
}
612+
613+
return $content;
614+
}
615+
616+
/**
617+
* Filter resources based on access patterns
618+
*
619+
* @param array $resources Array of resources to filter
620+
* @param array $allowedComponents Array of allowed component patterns
621+
* @return array Filtered resources
622+
*/
623+
private function filterResourcesByAccess($resources, $allowedComponents)
624+
{
625+
if (in_array('*', $allowedComponents)) {
626+
return $resources; // Full access
627+
}
628+
629+
$filtered = [];
630+
631+
foreach ($resources as $resource) {
632+
if (is_array($resource) && isset($resource['name'])) {
633+
$resourceName = $resource['name'];
634+
} elseif (is_string($resource)) {
635+
$resourceName = $resource;
636+
} else {
637+
continue;
638+
}
639+
640+
// Check if this resource is allowed
641+
if ($this->isResourceAllowed($resourceName, $allowedComponents)) {
642+
$filtered[] = $resource;
643+
}
644+
}
645+
646+
return $filtered;
647+
}
648+
649+
/**
650+
* Check if a resource is allowed based on component patterns
651+
*
652+
* @param string $resourceName Name of the resource to check
653+
* @param array $allowedComponents Array of allowed component patterns
654+
* @return bool True if resource is allowed
655+
*/
656+
private function isResourceAllowed($resourceName, $allowedComponents)
657+
{
658+
foreach ($allowedComponents as $pattern) {
659+
if ($pattern === '*') {
660+
return true;
661+
}
662+
663+
if ($pattern === $resourceName) {
664+
return true;
665+
}
666+
667+
// Handle wildcard patterns like "table/*"
668+
if (strpos($pattern, '*') !== false) {
669+
$pattern = str_replace('*', '.*', $pattern);
670+
$pattern = '/^' . $pattern . '$/';
671+
if (preg_match($pattern, $resourceName)) {
672+
return true;
673+
}
674+
}
675+
676+
// Handle dynamic parameters like "table/{id}"
677+
if (strpos($pattern, '{') !== false) {
678+
$regexPattern = preg_replace('/\{[^}]+\}/', '[^/]+', $pattern);
679+
$regexPattern = '/^' . $regexPattern . '$/';
680+
if (preg_match($regexPattern, $resourceName)) {
681+
return true;
682+
}
683+
}
684+
}
685+
686+
return false;
687+
}
688+
550689
/**
551690
* Make the service instance.
552691
*

src/Utility/Session.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,4 +1102,56 @@ public static function hasRole()
11021102

11031103
return \Session::get('has_role');
11041104
}
1105+
1106+
/**
1107+
* Get allowed components for GET operations on a specific service
1108+
*
1109+
* @param string $service Service name
1110+
* @param int $requestor Requestor type
1111+
* @return array Array of allowed component names/patterns
1112+
*/
1113+
public static function getAllowedComponentsForGet($service, $requestor = ServiceRequestorTypes::API)
1114+
{
1115+
if (static::isSysAdmin()) {
1116+
return ['*']; // Admin has access to everything
1117+
}
1118+
1119+
$roleId = Session::getRoleId();
1120+
if ($roleId && !Role::getCachedInfo($roleId, 'is_active')) {
1121+
return [];
1122+
}
1123+
1124+
$services = (array)static::get('role.services');
1125+
$service = strval($service);
1126+
$allowedComponents = [];
1127+
1128+
foreach ($services as $svcInfo) {
1129+
$tempRequestors = array_get($svcInfo, 'requestor_mask', ServiceRequestorTypes::API);
1130+
if (!($requestor & $tempRequestors)) {
1131+
continue;
1132+
}
1133+
1134+
$tempService = strval(array_get($svcInfo, 'service'));
1135+
$tempComponent = strval(array_get($svcInfo, 'component'));
1136+
$tempVerbs = array_get($svcInfo, 'verb_mask');
1137+
1138+
// Check if this service entry allows GET operations
1139+
if (0 == strcasecmp($service, $tempService) && ($tempVerbs & VerbsMask::GET_MASK)) {
1140+
if (empty($tempComponent)) {
1141+
$allowedComponents[] = '*';
1142+
} elseif ($tempComponent === '*') {
1143+
$allowedComponents[] = '*';
1144+
} elseif (strpos($tempComponent, '*') !== false) {
1145+
$allowedComponents[] = $tempComponent;
1146+
} elseif (strpos($tempComponent, '{') !== false) {
1147+
$allowedComponents[] = $tempComponent;
1148+
} else {
1149+
$allowedComponents[] = $tempComponent;
1150+
}
1151+
}
1152+
}
1153+
1154+
$result = array_unique($allowedComponents);
1155+
return $result;
1156+
}
11051157
}

0 commit comments

Comments
 (0)