/** * Remove a cabin * * @param array $info * @return string */ protected function removeCabin(array $info) : string { $ret = ''; $cabins = \Airship\loadJSON(ROOT . '/config/cabins.json'); $search = $this->makeNamespace($info['supplier'], $info['name']); foreach ($cabins as $i => $cabin) { if ($cabin['name'] === $search) { $ret .= "Removed {$search} from config/cabins.json\n"; $symlink = ROOT . '/Cabin/Bridge/Lens/cabin_links/' . $search; if (\is_link($symlink)) { \unlink($symlink); } unset($cabins[$i]); } } if (empty($ret)) { return 'Cabin not configured or missing.'; } $twigEnv = \Airship\configWriter(ROOT . 'config/templates'); // Save cabins.json \file_put_contents(ROOT . '/config/cabins.json', $twigEnv->render('cabins.twig', ['cabins' => $cabins])); /** * @security Watch this carefully: */ $ret .= \shell_exec('rm -rf ' . \escapeshellarg(ROOT . '/Cabin/' . $search)); return $ret; }
/** * Attempt to save the user-provided cabin configuration * * @param string $cabinName * @param array $cabins * @param array $post * @return bool */ protected function saveSettings(string $cabinName, array $cabins = [], array $post = []) : bool { $ds = DIRECTORY_SEPARATOR; $twigEnv = \Airship\configWriter(ROOT . $ds . 'config' . $ds . 'templates'); // Content-Security-Policy $csp = []; foreach ($post['content_security_policy'] as $dir => $rules) { if ($dir === 'upgrade-insecure-requests' || $dir === 'inherit') { continue; } if (empty($rules['allow'])) { $csp[$dir]['allow'] = []; } else { $csp[$dir]['allow'] = []; foreach ($rules['allow'] as $url) { if (!empty($url) && \is_string($url)) { $csp[$dir]['allow'][] = $url; } } } if (isset($rules['disable-security'])) { $csp[$dir]['allow'][] = '*'; } if ($dir === 'script-src') { $csp[$dir]['unsafe-inline'] = !empty($rules['unsafe-inline']); $csp[$dir]['unsafe-eval'] = !empty($rules['unsafe-eval']); $csp[$dir]['self'] = !empty($rules['self']); } elseif ($dir === 'style-src') { $csp[$dir]['unsafe-inline'] = !empty($rules['unsafe-inline']); $csp[$dir]['self'] = !empty($rules['self']); } elseif ($dir !== 'plugin-types') { $csp[$dir]['self'] = !empty($rules['self']); $csp[$dir]['data'] = !empty($rules['data']); } } $csp['inherit'] = !empty($post['content_security_policy']['inherit']); $csp['upgrade-insecure-requests'] = !empty($post['content_security_policy']['upgrade-insecure-requests']); $saveCabins = []; foreach ($cabins as $cab => $cab_data) { if ($cab_data['name'] !== $cabinName) { // Pass-through $saveCabins[$cab] = $cab_data; } else { $saveCabins[$post['config']['path']] = $post['config']; if (isset($cab_data['namespace'])) { // This should be immutable. $saveCabins[$post['config']['path']]['namespace'] = $cab_data['namespace']; } unset($saveCabins['path']); } } // Save CSP \file_put_contents(ROOT . $ds . 'config' . $ds . 'Cabin' . $ds . $cabinName . $ds . 'content_security_policy.json', \json_encode($csp, JSON_PRETTY_PRINT)); // Configuration if (!empty($post['cabin_manage_fallback'])) { $config_extra = \json_decode($post['config_extra'], true); $twig_vars = \json_decode($post['twig_vars'], true); } else { $config_extra = $post['config_extra']; $twig_vars = $post['twig_vars']; } if (!empty($config_extra)) { \Airship\saveJSON(ROOT . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'Cabin' . DIRECTORY_SEPARATOR . $cabinName . DIRECTORY_SEPARATOR . 'config.json', $config_extra); } if (!empty($twig_vars)) { \Airship\saveJSON(ROOT . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'Cabin' . DIRECTORY_SEPARATOR . $cabinName . DIRECTORY_SEPARATOR . 'twig_vars.json', $twig_vars); } // Clear the cache \unlink(ROOT . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'csp.' . $cabinName . '.json'); if (\extension_loaded('apcu')) { \apcu_clear_cache(); } // Save cabins.json \file_put_contents(ROOT . $ds . 'config' . $ds . 'cabins.json', $twigEnv->render('cabins.twig', ['cabins' => $saveCabins])); // Delete the cabin cache if (\file_exists(ROOT . 'tmp' . $ds . 'cache' . $ds . 'cabin_data.json')) { \unlink(ROOT . 'tmp' . $ds . 'cache' . $ds . 'cabin_data.json'); } return true; }
/** * Add the new cabin to config/cabins.json * * @param string $nameSpace * @param array $metadata * @return bool */ protected function updateCabinsRegistry(string $nameSpace, array $metadata) : bool { // Default route $defaultPath = ($metadata['default_path'] ?? '*/') . $this->supplier->getName() . '/' . $this->package; $twigEnv = \Airship\configWriter(ROOT . '/config/templates'); $cabins = \Airship\loadJSON(ROOT . '/config/cabins.json'); // We want to load everything before the wildcard entry: if (isset($cabins['*'])) { $newCabins = []; foreach (\array_keys($cabins) as $k) { if ($k !== '*') { $newCabins[$k] = $cabins[$k]; } } $newCabins[$defaultPath] = ['https' => false, 'canon_url' => '/' . $this->supplier->getName() . '/' . $this->package, 'language' => (string) ($metadata['lang'] ?? 'en-us'), 'name' => $nameSpace]; $newCabins['*'] = $cabins['*']; } else { $newCabins = $cabins; $newCabins[$defaultPath] = ['https' => false, 'canon_url' => '/' . $this->supplier->getName() . '/' . $this->package, 'language' => (string) ($metadata['lang'] ?? 'en-us'), 'name' => $nameSpace]; } return \file_put_contents(ROOT . '/config/cabins.json', $twigEnv->render('cabins.twig', ['cabins' => $newCabins])) !== false; }
/** * Save universal settings * * @param array $post * @return bool */ protected function saveSettings(array $post = []) : bool { $filterName = '\\Airship\\Cabin\\' . CABIN_NAME . '\\AirshipFilter'; if (\class_exists($filterName)) { $filter = new $filterName(); $post = $filter($post); } $twigEnv = \Airship\configWriter(ROOT . '/config/templates'); $csp = []; foreach ($post['content_security_policy'] as $dir => $rules) { if ($dir === 'upgrade-insecure-requests') { continue; } if (empty($rules['allow'])) { $csp[$dir]['allow'] = []; } else { $csp[$dir]['allow'] = []; foreach ($rules['allow'] as $url) { if (!empty($url) && \is_string($url)) { $csp[$dir]['allow'][] = $url; } } } if (isset($rules['disable-security'])) { $csp[$dir]['allow'][] = '*'; } if ($dir === 'script-src') { $csp[$dir]['unsafe-inline'] = !empty($rules['unsafe-inline']); $csp[$dir]['unsafe-eval'] = !empty($rules['unsafe-eval']); } elseif ($dir === 'style-src') { $csp[$dir]['unsafe-inline'] = !empty($rules['unsafe-inline']); } elseif ($dir !== 'plugin-types') { $csp[$dir]['self'] = !empty($rules['self']); $csp[$dir]['data'] = !empty($rules['data']); } } $csp['upgrade-insecure-requests'] = !empty($post['content_security_policy']['upgrade-insecure-requests']); if (isset($csp['inherit'])) { unset($csp['inherit']); } if ($post['universal']['ledger']['driver'] === 'database') { if (empty($post['universal']['ledger']['table'])) { // Table name must be provided. return false; } } // Save CSP \Airship\saveJSON(ROOT . '/config/content_security_policy.json', $csp); if (empty($post['universal']['guest_groups'])) { $post['universal']['guest_groups'] = []; } else { foreach ($post['universal']['guest_groups'] as $i => $g) { $post['universal']['guest_groups'][$i] = (int) $g; } } // Save universal config return \file_put_contents(ROOT . '/config/universal.json', $twigEnv->render('universal.twig', ['universal' => $post['universal']])) !== false; }
/** * Save all of the necessary configuration files */ protected function finalConfiguration() { $twigEnv = \Airship\configWriter(ROOT . '/config/templates'); \file_put_contents(ROOT . '/config/cabins.json', $this->finalConfigCabins($twigEnv)); \file_put_contents(ROOT . '/config/databases.json', $this->finalConfigDatabases($twigEnv)); \file_put_contents(ROOT . '/config/gadgets.json', '[]'); \file_put_contents(ROOT . '/config/universal.json', $this->finalConfigUniversal($twigEnv)); \chmod(ROOT . '/config/cabins.json', 0664); \chmod(ROOT . '/config/databases.json', 0664); \chmod(ROOT . '/config/gadgets.json', 0664); \chmod(ROOT . '/config/universal.json', 0664); }