config->getSystemValueBool('installed', false)) { $certs = \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; } else { $certs = $this->certificateManager->getAbsoluteBundlePath(); } $guzzleParams['verify'] = $certs; return $guzzleParams; } /** * @param DaemonConfig $daemonConfig * @return string|null * @throws \Exception */ public function getHarpSharedKey(array $deployConfig): ?string { try { return $this->crypto->decrypt($deployConfig['haproxy_password']); } catch (\Exception $e) { throw new \Exception('Failed to decrypt harp shared key', 0, $e); } } /** * @param DaemonConfig $daemonConfig * @return void * @throws \Exception */ protected function initGuzzleClient(DaemonConfig $daemonConfig): void { $guzzleParams = []; if ($daemonConfig->getProtocol() === 'https') { $guzzleParams = $this->setupCerts($guzzleParams); } if (!isset($daemonConfig->getDeployConfig()['haproxy_password']) || $daemonConfig->getDeployConfig()['haproxy_password'] === '') { throw new \Exception('Harp shared key is not set'); } $harpKey = $this->crypto->decrypt($daemonConfig->getDeployConfig()['haproxy_password']); if ($daemonConfig->getDeployConfig()['harp'] ?? false) { $guzzleParams['headers'] = [ 'harp-shared-key' => $harpKey, ]; } $this->guzzleClient = new Client($guzzleParams); } protected function buildHarpUrl(DaemonConfig $daemonConfig, string $path): string { return rtrim($daemonConfig->getProtocol() . '://' . $daemonConfig->getHost(), '/') . '/exapps/app_api/' . ltrim($path, '/'); } public static function isHarp(array $deployConfig): bool { return boolval($deployConfig['harp'] ?? false); } public static function isHarpDirectConnect(array $deployConfig): bool { return isset($deployConfig['harp']['exapp_direct']) && $deployConfig['harp']['exapp_direct'] === true; } public static function getExAppHost(ExApp $exApp): string { $deployConfig = $exApp->getDeployConfig(); if (HarpService::isHarpDirectConnect($deployConfig)) { if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST']) && $deployConfig['additional_options']['OVERRIDE_APP_HOST'] !== '' ) { $wideNetworkAddresses = ['0.0.0.0', '127.0.0.1', '::', '::1']; if (!in_array($deployConfig['additional_options']['OVERRIDE_APP_HOST'], $wideNetworkAddresses)) { return $deployConfig['additional_options']['OVERRIDE_APP_HOST']; } } if ($exApp->getAcceptsDeployId() !== ManualActions::DEPLOY_ID) { return $exApp->getAppid(); } } return '127.0.0.1'; } public function getHarpExApp(ExApp $exApp): array { return [ 'exapp_token' => $exApp->getSecret(), 'exapp_version' => $exApp->getVersion(), 'host' => $this->getExAppHost($exApp), 'port' => $exApp->getPort(), 'routes' => array_map(function ($route) { $bruteforceList = json_decode($route['bruteforce_protection'], true); if (!$bruteforceList) { $bruteforceList = []; } return [ 'url' => $route['url'], 'access_level' => $route['access_level'], 'bruteforce_protection' => $bruteforceList, ]; }, $exApp->getRoutes()), ]; } public function harpExAppUpdate(DaemonConfig $daemonConfig, ExApp $exApp, bool $added): void { if (!self::isHarp($daemonConfig->getDeployConfig())) { return; } $this->initGuzzleClient($daemonConfig); $appId = $exApp->getAppid(); $url = $this->buildHarpUrl($daemonConfig, "/exapp_storage/$appId"); $addedStr = $added ? 'add' : 'remove'; $this->logger->info("HarpService: harpExAppUpdate ($addedStr): " . $url); try { if ($added) { $this->guzzleClient->post($url, [ 'json' => $this->getHarpExApp($exApp), ]); } else { $this->guzzleClient->delete($url); } } catch (ClientException $e) { if (!$added && $e->getResponse()->getStatusCode() === 404) { $this->logger->info("HarpService: harpExAppUpdate ($addedStr) - 404 Not Found: " . $url); } else { $this->logger->error("HarpService: harpExAppUpdate ($addedStr) failed: " . $e->getMessage()); } } catch (\Exception $e) { $this->logger->error("HarpService: harpExAppUpdate ($addedStr) failed: " . $e->getMessage()); } } public function getHarpVersion(DaemonConfig $daemonConfig): ?string { if (!self::isHarp($daemonConfig->getDeployConfig())) { return null; } $this->initGuzzleClient($daemonConfig); $url = $this->buildHarpUrl($daemonConfig, '/info'); $this->logger->info('HarpService: getHarpVersion: ' . $url); try { $response = $this->guzzleClient->get($url); $data = json_decode($response->getBody()->getContents(), true); if (isset($data['version'])) { if (gettype($data['version']) === 'double') { return $this->versionFloatToString($data['version']); } return (string) $data['version']; } return null; } catch (\Exception $e) { $this->logger->error('HarpService: getHarpVersion failed: ' . $e->getMessage()); return null; } } private function versionFloatToString(float $version): string { // If the Harp version was reported as a float, e.g. `4.2`, convert it to the string "4.2.0". // We use number_format to avoid potential issues from locale-dependent float to string conversions. return rtrim(number_format($version, 10, '.', ''), '0') . '.0'; } }