diff --git a/packages/web/src/Router/Route.php b/packages/web/src/Router/Route.php index 53160071de..4f6cacf36f 100644 --- a/packages/web/src/Router/Route.php +++ b/packages/web/src/Router/Route.php @@ -913,10 +913,24 @@ public function __construct() $webrootbase = '/' . ($webrootbase === '' ? '' : $webrootbase . '/'); self::$_webrootbase = $webrootbase; + $requripath = strtok((string)self::$requesturi, '?'); /** * If API is not enabled redirect to home page. + * + * Not the agent routes. FOG_API_ENABLED switches off the REST API + * that people and API tokens use; /agent/v1/ is FOG's client + * channel, which the setting never governed for the legacy client + * (service/*.php) either. Caught here, every fog-agent on a server + * with the API off was sent a 308 to the login page and could never + * enroll or poll (forum topic 18241). The agent routes keep their + * own gate below: enroll issues nothing without an approval, and + * everything else needs a certificate bound to a host. */ - if (!self::$ajax && !self::$_enabled) { + $isAgentRoute = 0 === strpos( + $requripath, + $webrootbase . self::AGENT_ROUTE_SEGMENT + ); + if (!self::$ajax && !self::$_enabled && !$isAgentRoute) { header( sprintf( 'Location: %s://%s%smanagement/index.php', @@ -975,7 +989,6 @@ public function __construct() } $unauthexact[] = $webrootbase . ltrim($pluginRoute['path'], '/'); } - $requripath = strtok((string)self::$requesturi, '?'); $requribase = dirname($requripath); $isunauth = in_array($requribase, $unauthprefixes) || in_array(rtrim($requripath, '/'), $unauthexact); diff --git a/tests/agent-routes-ignore-api-switch.test.php b/tests/agent-routes-ignore-api-switch.test.php new file mode 100644 index 0000000000..50720b7d96 --- /dev/null +++ b/tests/agent-routes-ignore-api-switch.test.php @@ -0,0 +1,161 @@ + " + * + * @return void + */ +function apiSwitchRunChild($case) +{ + list($flag, $uri) = explode(' ', $case, 2); + FogTestHarness::boot('agent-routes-ignore-api-switch'); + $db = FogTestHarness::fakeDb(); + $db->responder = function ($sql) use ($flag) { + if (false === strpos($sql, 'globalSettings')) { + return null; + } + return [ + ['settingKey' => 'FOG_API_ENABLED', 'settingValue' => $flag], + ['settingKey' => 'FOG_API_TOKEN', 'settingValue' => 'test'], + ['settingKey' => 'FOG_WEB_ROOT', 'settingValue' => '/fog/'], + ]; + }; + foreach ( + [ + '_initialized' => true, + 'requesturi' => $uri, + 'reqmethod' => 'POST', + 'post' => true, + 'ajax' => false, + 'httpproto' => 'https', + 'httphost' => '192.168.1.100', + 'remoteaddr' => '192.168.102.20', + 'scriptname' => '/fog/api/index.php', + 'querystring' => '', + ] as $property => $value + ) { + FogTestHarness::setStatic('FOGBase', $property, $value); + } + ob_start(); + register_shutdown_function( + function () { + $body = trim((string)ob_get_clean()); + echo 'RESULT ' . (int)http_response_code() . ' ' + . str_replace("\n", ' ', $body) . "\n"; + } + ); + new Route(); +} + +/** + * Run one request in a child process. + * + * @param string $flag FOG_API_ENABLED + * @param string $uri the request uri + * + * @return array [int status (0 when the child printed nothing), string body] + */ +function apiSwitchChild($flag, $uri) +{ + $pipes = []; + $proc = proc_open( + [PHP_BINARY, __FILE__, '--case=' . $flag . ' ' . $uri], + [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], + $pipes + ); + if (!is_resource($proc)) { + return [0, 'SPAWN FAILED']; + } + fclose($pipes[0]); + $out = stream_get_contents($pipes[1]); + $err = stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($proc); + if (preg_match('/^RESULT (\d+) ?(.*)$/m', (string)$out, $m)) { + return [(int)$m[1], $m[2]]; + } + return [0, 'NO RESULT: ' . trim(str_replace("\n", ' | ', $out . ' ' . $err))]; +} + +$t = new FogChecks(); + +list($code, $body) = apiSwitchChild('0', '/fog/agent/v1/enroll'); +$t->check( + "enroll with the API off is not redirected (got $code $body)", + 0 !== $code && ($code < 300 || $code >= 400) +); + +list($code, $body) = apiSwitchChild('0', '/fog/agent/v1/poll'); +$t->check( + "poll with the API off reaches the agent gate and gets 401 (got $code)", + 401 === $code +); +$t->check( + 'and the agent gate is what answered, not a token check', + false !== strpos($body, '"reason":"no_client_certificate"') +); + +list($code, $body) = apiSwitchChild('0', '/fog/host'); +$t->check( + "an API route with the API off is still redirected (got $code)", + 308 === $code +); + +$t->finish();