/** * Dependency injection aside from the controller. Allows you to write your * own constructors. * * This is final so nobody changes it in a Gear. Please don't mess with * this component. * * @param Lens $lens * @param array $databases * @param string $urlPrefix */ public final function airshipEjectFromCockpit(Lens $lens, array $databases = [], string $urlPrefix = '') { $this->airship_http_method = $_SERVER['REQUEST_METHOD']; $this->airship_lens_object = $lens; $this->airship_databases = $databases; $this->airship_csrf = Gears::get('CSRF'); $this->airship_cabin_prefix = \rtrim('/' . $urlPrefix, '/'); $file = ROOT . '/config/Cabin/' . \CABIN_NAME . '/config.json'; if (\file_exists($file)) { $this->airship_config = \Airship\loadJSON($file); } $this->airshipLand(); }
/** * Reload the signing keys * * @param array $data * @return Supplier */ public function reloadSigningKeys(array $data = []) : self { if (empty($data)) { $data = \Airship\loadJSON(ROOT . '/config/supplier_keys/' . $this->name . '.json'); } if (isset($data['signing_keys'])) { $keys = []; foreach ($data['signing_keys'] as $sk) { $keys[] = ['type' => $sk['type'], 'key' => new SignaturePublicKey(\Sodium\hex2bin($sk['public_key']))]; } $this->signing_keys = $keys; } return $this; }
/** * @route gadgets/universal */ public function manageUniversal() { $cabins = $this->getCabinNamespaces(); $gadgets = \Airship\loadJSON(ROOT . '/config/gadgets.json'); if (!$this->can('update')) { \Airship\redirect($this->airship_cabin_prefix . '/gadgets'); } $post = $this->post(GadgetsFilter::fromConfig(\array_keys($gadgets))); if ($post) { if ($this->updateUniversalGadgets($gadgets, $post)) { \Airship\clear_cache(); \Airship\redirect($this->airship_cabin_prefix . '/gadgets/universal'); } } $this->lens('gadget_manage', ['cabins' => $cabins, 'gadgets' => $gadgets, 'title' => \__('Manage Universal Gadgets')]); }
/** * Add the new motif as an option to the Cabin * * @param string $cabin * @return bool */ protected function addMotifToCabin(string $cabin) : bool { if (!\is_dir(ROOT . '/Cabin/' . $cabin)) { return false; } $supplier = $this->supplier->getName(); $name = $this->package; $configFile = ROOT . '/Cabin/' . $cabin . '/config/motifs.json'; $config = \Airship\loadJSON($configFile); $i = 1; while (isset($config[$name])) { $name = $this->package . '-' . ++$i; } $config[$name] = ['enabled' => true, 'supplier' => $supplier, 'name' => $this->package, 'path' => $supplier . '/' . $this->package]; if (!$this->createSymlinks($cabin, $config, $name)) { // This isn't essential. $this->log('Could not create symlinks for Cabin "' . $cabin . '".', LogLevel::WARNING); } return \Airship\saveJSON($configFile, $config); }
/** * Lookup a string from a language file * * @param string $key * @param string $lang * @param mixed[] ...$params * @return string * @throws TranslationKeyNotFound */ public function lookup(string $key, string $lang = 'en-us', ...$params) : string { if (!\array_key_exists($lang, $this->phrases)) { $this->phrases[$lang] = \Airship\loadJSON(ROOT . '/lang/' . $lang . '.json'); } $split_key = \explode('.', $key); $v = $this->phrases[$lang]; foreach ($split_key as $k) { if (!\array_key_exists($k, $v)) { throw new TranslationKeyNotFound($key); } $v = $v[$k]; } $str = ''; while (empty($str)) { /** @noinspection PhpUsageOfSilenceOperatorInspection */ $str = @\sprintf($v, ...$params); \array_push($params, ''); } return $str; }
* * @global State $state */ /** * First, Content-Security-Policy headers: */ $cspCacheFile = ROOT . '/tmp/cache/csp.' . AutoPilot::$active_cabin . '.json'; if (\file_exists($cspCacheFile) && \filesize($cspCacheFile) > 0) { $csp = CSPBuilder::fromFile($cspCacheFile); } else { $cspfile = ROOT . '/config/Cabin/' . AutoPilot::$active_cabin . '/content_security_policy.json'; if (\file_exists($cspfile)) { $cabinPolicy = \Airship\loadJSON($cspfile); // Merge the cabin-specific policy with the base policy if (!empty($cabinPolicy['inherit'])) { $basePolicy = \Airship\loadJSON(ROOT . '/config/content_security_policy.json'); $cabinPolicy = \Airship\csp_merge($cabinPolicy, $basePolicy); } \Airship\saveJSON($cspCacheFile, $cabinPolicy); $csp = CSPBuilder::fromFile($cspCacheFile); } else { // No cabin policy, use the default $csp = CSPBuilder::fromFile(ROOT . '/config/content_security_policy.json'); } } $state->CSP = $csp; /** * Next, if we're connected over HTTPS, send an HPKP header too: */ if (AutoPilot::isHTTPSConnection()) { $hpkpCacheFile = ROOT . '/tmp/cache/hpkp.' . AutoPilot::$active_cabin . '.json';
/** * @route help */ public function helpPage() { if ($this->isLoggedIn()) { $this->storeLensVar('showmenu', true); // $cabins = $this->getCabinNamespaces(); // Get debug information. $helpInfo = ['cabins' => [], 'cabin_names' => \array_values($cabins), 'gears' => [], 'universal' => []]; /** * This might reveal "sensitive" information. By default, it's * locked out of non-administrator users. You can grant access to * other users/groups via the Permissions menu. */ if ($this->can('read')) { $state = State::instance(); if (\is_readable(ROOT . '/config/gadgets.json')) { $helpInfo['universal']['gadgets'] = \Airship\loadJSON(ROOT . '/config/gadgets.json'); } if (\is_readable(ROOT . '/config/content_security_policy.json')) { $helpInfo['universal']['content_security_policy'] = \Airship\loadJSON(ROOT . '/config/content_security_policy.json'); } foreach ($cabins as $cabin) { $cabinData = ['config' => \Airship\loadJSON(ROOT . '/Cabin/' . $cabin . '/manifest.json'), 'content_security_policy' => [], 'gadgets' => [], 'motifs' => [], 'user_motifs' => \Airship\LensFunctions\user_motif($this->getActiveUserId(), $cabin)]; $prefix = ROOT . '/Cabin/' . $cabin . '/config/'; if (\is_readable($prefix . 'gadgets.json')) { $cabinData['gadgets'] = \Airship\loadJSON($prefix . 'gadgets.json'); } if (\is_readable($prefix . 'motifs.json')) { $cabinData['motifs'] = \Airship\loadJSON($prefix . 'motifs.json'); } if (\is_readable($prefix . 'content_security_policy.json')) { $cabinData['content_security_policy'] = \Airship\loadJSON($prefix . 'content_security_policy.json'); } $helpInfo['cabins'][$cabin] = $cabinData; } $helpInfo['gears'] = []; foreach ($state->gears as $gear => $latestGear) { $helpInfo['gears'][$gear] = \Airship\get_ancestors($latestGear); } // Only grab data likely to be pertinent to common issues: $keys = ['airship', 'auto-update', 'debug', 'guzzle', 'notary', 'rate-limiting', 'session_config', 'tor-only', 'twig_cache']; $helpInfo['universal']['config'] = \Airship\keySlice($state->universal, $keys); $helpInfo['php'] = ['halite' => Halite::VERSION, 'libsodium' => ['major' => \Sodium\library_version_major(), 'minor' => \Sodium\library_version_minor(), 'version' => \Sodium\version_string()], 'version' => \PHP_VERSION, 'versionid' => \PHP_VERSION_ID]; } $this->lens('help', ['active_link' => 'bridge-link-help', 'airship' => \AIRSHIP_VERSION, 'helpInfo' => $helpInfo]); } else { // Not a registered user? Go read the docs. No info leaks for you! \Airship\redirect('https://github.com/paragonie/airship-docs'); } }
/** * Fetch a query string from the stored queries file * * @param string $index Which index to replace * @param array $params Parameters to be replaced in the query string * @param string $cabin Which Cabin are we loading? * @param string $driver Which database driver? * @return string * @throws NotImplementedException */ function queryString(string $index, array $params = [], string $cabin = \CABIN_NAME, string $driver = '') : string { static $_cache = []; if (empty($driver)) { $db = \Airship\get_database(); $driver = $db->getDriver(); } $cacheKey = Util::hash($cabin . '/' . $driver, \Sodium\CRYPTO_GENERICHASH_BYTES_MIN); if (empty($_cache[$cacheKey])) { $driver = \preg_replace('/[^a-z]/', '', \strtolower($driver)); $path = !empty($cabin) ? ROOT . '/Cabin/' . $cabin . '/Queries/' . $driver . '.json' : ROOT . '/Engine/Queries/' . $driver . '.json'; $_cache[$cacheKey] = \Airship\loadJSON($path); } $split_key = \explode('.', $index); $v = $_cache[$cacheKey]; foreach ($split_key as $k) { if (!\array_key_exists($k, $v)) { throw new NotImplementedException(\trk('errors.database.query_not_found', $index)); } $v = $v[$k]; } if (\is_array($v)) { throw new NotImplementedException(\trk('errors.database.multiple_candidates', $index)); } $str = $v; foreach ($params as $token => $replacement) { $str = \str_replace('{{' . $token . '}}', $replacement, $str); } return $str; }
/** * Install an updated version of a cabin * * If we get to this point: * * 1. We know the signature is signed by the supplier. * 2. The hash was checked into Keyggdrasil, which * was independently vouched for by our peers. * * @param UpdateInfo $info * @param UpdateFile $file * @throws CouldNotUpdate */ protected function install(UpdateInfo $info, UpdateFile $file) { if (!$file->hashMatches($info->getChecksum())) { throw new CouldNotUpdate(\__('Checksum mismatched')); } $path = $file->getPath(); $this->log('Begin Cabin updater', LogLevel::DEBUG, ['path' => $path, 'supplier' => $info->getSupplierName(), 'name' => $info->getPackageName()]); $updater = new \Phar($path, \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME); $updater->setAlias($this->pharAlias); $ns = $this->makeNamespace($info->getSupplierName(), $info->getPackageName()); // We need to do this while we're replacing files. $this->bringCabinDown($ns); $oldMetadata = \Airship\loadJSON(ROOT . '/Cabin/' . $ns . '/manifest.json'); // Overwrite files $updater->extractTo(ROOT . '/Cabin/' . $ns, null, true); // Run the update trigger. Sandbox::safeInclude('phar://' . $this->pharAlias . '/update_trigger.php', $oldMetadata); // Free up the updater alias $garbageAlias = Base64UrlSafe::encode(\random_bytes(33)) . '.phar'; $updater->setAlias($garbageAlias); unset($updater); // Now bring it back up. $this->bringCabinBackUp($ns); // Make sure we update the version info. in the DB cache: $this->updateDBRecord('Cabin', $info); $this->log('Conclude Cabin updater', LogLevel::DEBUG, ['path' => $path, 'supplier' => $info->getSupplierName(), 'name' => $info->getPackageName()]); self::$continuumLogger->store(LogLevel::INFO, 'Cabin update installed', $this->getLogContext($info, $file)); }
/** * @param bool $deepSort * @return array */ protected function getAllMotifs(bool $deepSort = false) : array { $allMotifs = []; $cabins = $this->getCabinNamespaces(); foreach ($cabins as $cabinName) { $motifs = \Airship\loadJSON(ROOT . '/Cabin/' . $cabinName . '/config/motifs.json'); foreach ($motifs as $motif => $config) { if (!\array_key_exists($motif, $allMotifs)) { $allMotifs[$motif] = $config; $allMotifs[$motif]['link'] = $motif; } } } if ($deepSort) { \usort($allMotifs, function (array $a, array $b) : int { if ($a['supplier'] === $b['supplier']) { return (int) ($a['name'] <=> $b['name']); } return (int) ($a['supplier'] <=> $b['supplier']); }); } else { \ksort($allMotifs); } return $allMotifs; }
// Load the template variables for this Cabin: if (\file_exists(ROOT . '/config/Cabin/' . $active['name'] . '/twig_vars.json')) { $_settings = \Airship\loadJSON(ROOT . '/config/Cabin/' . $active['name'] . '/twig_vars.json'); $lens->addGlobal('SETTINGS', $_settings); } // Now let's load all the lens.php files, which are added by Gadgets: foreach ($lensLoad as $incl) { include $incl; } /** * Let's load up the databases */ $dbPool = []; require ROOT . '/database.php'; // Airship manifest: $manifest = \Airship\loadJSON(ROOT . '/config/manifest.json'); $state->manifest = $manifest; $htmlpurifier = new \HTMLPurifier(\HTMLPurifier_Config::createDefault()); $state->HTMLPurifier = $htmlpurifier; /** * Load up all of the keys used by the application: */ require_once ROOT . '/keys.php'; /** * Set up the logger */ require_once ROOT . '/config/logger.php'; /** * Automatic security updates */ $hail = Gears::get('Hail', new HTTPClient($state->universal['guzzle']));
/** * Load a JSON configuration file * * @param string $name * @param string $ds * @return array */ protected function loadJSONConfigFile(string $name, string $ds = DIRECTORY_SEPARATOR) : array { try { return \Airship\loadJSON(ROOT . $ds . 'config' . $ds . $name); } catch (FileNotFound $ex) { return []; } // Let all other errors throw }
<?php declare (strict_types=1); use Airship\Engine\{Database, Gears, State}; /** * Set up the Database objects from our database.json file * * @global State $state */ $dbgear = Gears::getName('Database'); $databases = \Airship\loadJSON(ROOT . '/config/databases.json'); $dbPool = []; $dbCount = 0; // Needed for IDE code completion: if (IDE_HACKS) { $dbgear = new Database(new \PDO('sqlite::memory:')); } /** * Initialize all of our database connections: */ foreach ($databases as $label => $dbs) { $dbPool[$label] = []; foreach ($dbs as $dbConf) { if (isset($dbConf['driver']) || isset($dbConf['dsn'])) { $conf = [isset($dbConf['dsn']) ? $dbConf['dsn'] : $dbConf]; if (isset($dbConf['username']) && isset($dbConf['password'])) { $conf[] = $dbConf['username']; $conf[] = $dbConf['password']; if (isset($dbConf['options'])) { $conf[] = $dbConf['options']; }
<?php declare (strict_types=1); use Airship\Engine\{Gears, Hail, Keyggdrasil, State}; /** * Keyggdrasil updater -- either throw this in a cronjob or let it get * triggered every time a page loads after enough time has elapsed * * @global State $state * @global Hail $hail */ \ignore_user_abort(true); \set_time_limit(0); require_once \dirname(__DIR__) . '/bootstrap.php'; if (\is_readable(ROOT . '/config/databases.json')) { /** * Initialize the channel updater service */ $channels = \Airship\loadJSON(ROOT . '/config/channels.json'); $database = \Airship\get_database(); $state->logger->info('Keyggdrasil started'); $keyUpdater = Gears::get('TreeUpdater', $hail, $database, $channels); if (IDE_HACKS) { $keyUpdater = new Keyggdrasil($hail, $database, $channels); } $keyUpdater->doUpdate(); $state->logger->info('Keyggdrasil concluded'); } else { // We can't update keys without a place to persist the changes }
/** * We're storing a new public key for this supplier. * * @param Channel $chan * @param TreeUpdate $update * @return void */ protected function revokeKey(Channel $chan, TreeUpdate $update) { $supplier = $update->getSupplier(); $name = $supplier->getName(); $file = ROOT . '/config/supplier_keys/' . $name . '.json'; $supplierData = \Airship\loadJSON($file); foreach ($supplierData['signing_keys'] as $id => $skey) { if (\hash_equals($skey['public_key'], $update->getPublicKeyString())) { // Remove this key unset($supplierData['signing_keys'][$id]); break; } } \Airship\saveJSON($file, $supplierData); \clearstatcache(); // Flush the channel's supplier cache $chan->getSupplier($name, true); }
$link = ROOT . '/public/static/' . $active['name']; if (!\is_link($link)) { // Remove copies, we only allow symlinks in static if (\is_dir($link)) { \rmdir($link); } elseif (\file_exists($link)) { \unlink($link); } // Create a symlink from public/static/* to Cabin/*/public /** @noinspection PhpUsageOfSilenceOperatorInspection */ @\symlink(CABIN_DIR . '/public', ROOT . '/public/static/' . $active['name']); } } // Let's load the default cargo modules if (\is_dir(CABIN_DIR . '/Lens/cargo')) { $cargoCacheFile = ROOT . '/tmp/cache/cargo-' . $active['name'] . '.cache.json'; if (\file_exists($cargoCacheFile)) { $data = Airship\loadJSON($cargoCacheFile); $state->cargo = $data; } else { $dir = \getcwd(); \chdir(CABIN_DIR . '/Lens'); foreach (\Airship\list_all_files('cargo', 'twig') as $cargo) { $idx = \str_replace(['__', '/'], ['', '__'], Binary::safeSubstr($cargo, 6, -5)); Gadgets::loadCargo($idx, $cargo); } \chdir($dir); // Store the cache file \Airship\saveJSON($cargoCacheFile, $state->cargo); } }
/** * Gadget install process. * * 1. Move .phar to the appropriate location. * 2. If this gadget is for a particular cabin, add it to that cabin's * gadgets.json file. * 3. Run the update triggers (install hooks and incremental upgrades). * 4. Clear the cache files. * * @param InstallFile $fileInfo * @return bool */ public function install(InstallFile $fileInfo) : bool { $supplier = $this->supplier->getName(); $fileName = $supplier . '.' . $this->package . '.phar'; $metadata = $this->getMetadata($fileInfo); // Move .phar file to its destination. if (!empty($metadata['cabin'])) { $gadgetConfigFile = ROOT . '/Cabin/' . $metadata['cabin'] . '/config/gadgets.json'; // Cabin-specific gadget $cabin = ROOT . '/Cabin/' . $metadata['cabin'] . '/Gadgets'; if (!\is_dir($cabin)) { $this->log('Could not install; cabin "' . $metadata['cabin'] . '" is not installed.', LogLevel::ERROR); return false; } $filePath = $cabin . '/' . $supplier . '/' . $fileName; if (!\is_dir($cabin . '/' . $supplier)) { \mkdir($cabin . '/' . $supplier, 0775); } } else { $gadgetConfigFile = ROOT . '/config/gadgets.json'; // Universal gadget. (Probably affects the Engine.) $filePath = ROOT . '/Gadgets/' . $supplier . '/' . $fileName; if (!\is_dir(ROOT . '/Gadgets/' . $supplier)) { \mkdir(ROOT . '/Gadgets/' . $supplier, 0775); } } $gadgetConfig = \Airship\loadJSON($gadgetConfigFile); $gadgetConfig[] = [['supplier' => $supplier, 'name' => $this->package, 'version' => $metadata['version'] ?? null, 'path' => $filePath, 'enabled' => true]]; \Airship\saveJSON($gadgetConfigFile, $gadgetConfig); \rename($fileInfo->getPath(), $filePath); // If cabin-specific, add to the cabin's gadget.json if ($metadata['cabin']) { $this->addToCabin($metadata['cabin']); } // Run the update hooks: $alias = 'gadget.' . $fileName; $phar = new \Phar($filePath, \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME); $phar->setAlias($alias); // Run the update trigger. if (\file_exists('phar://' . $alias . '/update_trigger.php')) { Sandbox::safeRequire('phar://' . $alias . '/update_trigger.php'); } self::$continuumLogger->store(LogLevel::INFO, 'Install successful', $this->getLogContext($fileInfo)); // Finally, clear the cache files: return $this->clearCache(); }
// SKIP! We have a potential directory traversal continue; } // Create the necessary symlinks if they do not already exist: if (!\is_link($motifStart)) { \symlink($motifEnd . '/lens', $motifStart); } if (\is_dir($motifEnd . '/public')) { $motifPublic = CABIN_DIR . '/public/motif/' . $motif; if (!\is_link($motifPublic)) { \symlink($motifEnd . '/public', $motifPublic); } } // Finally, load the configuration: if (\file_exists($motifEnd . '/motif.json')) { $motifConfig['config'] = \Airship\loadJSON($motifEnd . '/motif.json'); } else { $motifConfig['config'] = []; } $motifs[$motif] = $motifConfig; } } \Airship\saveJSON($motifCacheFile, $motifs); $state->motifs = $motifs; } else { die(\__("FATAL ERROR: Motifs file is not readable")); } } } $userMotif = \Airship\LensFunctions\user_motif(); if (!empty($userMotif)) {
<?php declare (strict_types=1); use ParagonIE\Halite\KeyFactory; /** * This sets up the contents of our keyring. */ $key_management_closure = function () { if (!\is_dir(ROOT . '/config/keyring/')) { \mkdir(ROOT . '/config/keyring/', 0750); } $keyRing = \Airship\loadJSON(ROOT . '/config/keyring.json'); if (empty($keyRing)) { // This is critical to Airship's functioning. throw new \Error(\trk('errors.crypto.keyring_missing')); } $state = \Airship\Engine\State::instance(); $keys = []; foreach ($keyRing as $index => $keyConfig) { $path = ROOT . '/config/keyring/' . $keyConfig['file']; if (\file_exists($path)) { // Load it from disk switch ($keyConfig['type']) { case 'AuthenticationKey': $keys[$index] = KeyFactory::loadAuthenticationKey($path); break; case 'EncryptionKey': $keys[$index] = KeyFactory::loadEncryptionKey($path); break; case 'EncryptionPublicKey': $keys[$index] = KeyFactory::loadEncryptionPublicKey($path);
/** * We are removing a key from our trust store. * * @param array $keyData * @param array $nodeData * @return bool * @throws InvalidType */ protected function revokeKey(array $keyData, array $nodeData) : bool { $supplier = \preg_replace('/[^A-Za-z0-9_\\-]/', '', $keyData['supplier']); if (empty($supplier)) { throw new InvalidType(\__('Expected non-empty string for supplier name')); } $filePath = ROOT . '/config/supplier_keys/' . $supplier . '.json'; if (\file_exists($filePath)) { $supplierData = \Airship\loadJSON($filePath); if (!$this->verifyMasterSignature($supplierData, $keyData, $nodeData)) { return false; } // Remove the revoked key. foreach ($supplierData['signing_keys'] as $i => $key) { if (\hash_equals($keyData['public_key'], $key['public_key'])) { unset($supplierData['signing_keys'][$i]); break; } } return \file_put_contents($filePath, \json_encode($supplierData, JSON_PRETTY_PRINT)) !== false; } // Fail closed: return false; }
/** * Update the version identifier stored in the gadgets.json file * * @param UpdateInfo $info * @param array $metaData */ public function updateJSON(UpdateInfo $info, array $metaData = []) { if (!empty($metaData['cabin'])) { $gadgetConfigFile = ROOT . '/Cabin/' . $metaData['cabin'] . '/config/gadgets.json'; } else { $gadgetConfigFile = ROOT . '/config/gadgets.json'; } $gadgetConfig = \Airship\loadJSON($gadgetConfigFile); foreach ($gadgetConfig as $i => $gadget) { if ($gadget['supplier'] === $info->getSupplierName()) { if ($gadget['name'] === $info->getPackageName()) { $gadgetConfig[$i]['version'] = $info->getVersion(); break; } } } \Airship\saveJSON($gadgetConfigFile, $gadgetConfig); }
/** * Load all of the supplier's Ed25519 public keys * * @param string $supplier * @param boolean $force_flush * @return SupplierObject|SupplierObject[] * @throws NoSupplier */ public function getSupplier(string $supplier = '', bool $force_flush = false) { if (empty($supplier)) { // Fetch all suppliers if ($force_flush || empty($this->supplierCache)) { $supplierCache = []; $allSuppliers = \Airship\list_all_files(ROOT . '/config/supplier_keys', 'json'); foreach ($allSuppliers as $supplierKeyFile) { // We want everything except the .json $supplier = $this->escapeSupplierName(Binary::safeSubstr($this->getEndPiece($supplierKeyFile), 0, -5)); try { $data = \Airship\loadJSON($supplierKeyFile); } catch (FileNotFound $ex) { $data = []; } $supplierCache[$supplier] = new SupplierObject($supplier, $data); } $this->supplierCache = $supplierCache; } return $this->supplierCache; } // Otherwise, we're just fetching one supplier's keys if ($force_flush || empty($this->supplierCache[$supplier])) { try { $supplier = $this->escapeSupplierName($supplier); $supplierFile = ROOT . '/config/supplier_keys/' . $supplier . '.json'; if (!\file_exists($supplierFile)) { throw new NoSupplier(\__("Supplier file not found: %s", "default", $supplierFile)); } $data = \Airship\loadJSON($supplierFile); } catch (FileNotFound $ex) { throw new NoSupplier(\__("Supplier not found: %s", "default", $supplier), 0, $ex); } $this->supplierCache[$supplier] = new SupplierObject($supplier, $data); } if (isset($this->supplierCache[$supplier])) { return $this->supplierCache[$supplier]; } throw new NoSupplier(); }
/** * Get all of the motifs that belong to a particular cabin * * @param string $cabin * @return array */ protected function getCabinsMotifs(string $cabin) : array { $config = \Airship\loadJSON(ROOT . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'Cabin' . DIRECTORY_SEPARATOR . $cabin . DIRECTORY_SEPARATOR . 'motifs.json'); $motifs = []; foreach ($config as $name => $conf) { $json = \Airship\loadJSON(ROOT . '/Motifs/' . $conf['path'] . '/motif.json'); $motifs[$name] = ['path' => $conf['path'], 'config' => $json]; } return $motifs; }
/** * Load the config for this motif * * @param string $name * @return self */ public function loadMotifConfig(string $name) : self { $state = State::instance(); if (isset($state->motifs[$name])) { try { if (\file_exists(ROOT . '/config/motifs/' . $name . '.json')) { $state->motif_config = \Airship\loadJSON(ROOT . '/config/motifs/' . $name . '.json'); } else { $state->motif_config = []; } } catch (\Throwable $ex) { $state->motif_config = []; } } return $this; }
/** * Get an array of GadgetUpdater objects * * @return MotifUpdater[] */ public function getMotifs() : array { $motifs = []; // First the universal gadgets: foreach (\glob(ROOT . '/Motifs/*') as $supplierPath) { if (!\is_dir($supplierPath)) { continue; } $supplier = $this->getEndPiece($supplierPath); foreach (\glob($supplierPath . '/*') as $motifDir) { if ($motifDir === ROOT . '/Motifs/paragonie/airship-classic') { continue; } $motifName = $this->getEndPiece($motifDir); $manifest = \Airship\loadJSON($motifDir . '/motif.json'); $name = $supplier . '.' . $motifName; $motifs[$name] = new MotifUpdater($this->hail, $manifest, $this->getSupplier($manifest['supplier'])); } } return $motifs; }
$state->active_cabin = $key; if ($cabin['enabled']) { $cabinDisabled = true; } break; } } $state->cabins = $config['cabins']; } else { // Load the cabins, rebuild the cache $cabins = \Airship\loadJSON(ROOT . '/config/cabins.json'); $active_cabin = null; foreach ($cabins as $key => $cabin) { try { $cabinName = !empty($cabin['namespace']) ? $cabin['namespace'] : $cabin['name']; $cabin['data'] = \Airship\loadJSON(\implode('/', [ROOT, 'Cabin', $cabinName, 'manifest.json'])); // Link configuration directory: $startLink = \implode('/', [ROOT, 'config', 'Cabin', $cabinName]); if (!\is_link($startLink)) { $endLink = \implode('/', [ROOT, 'Cabin', $cabinName, 'config']); \symlink($endLink, $startLink); } // Link configuration template: $startLink = \implode('/', [ROOT, 'config', 'templates', 'Cabin', $cabinName]); if (!\is_link($startLink)) { $endLink = \implode('/', [ROOT, 'Cabin', $cabinName, 'config', 'templates']); \symlink($endLink, $startLink); } // Expose common template snippets to the template loader: $startLink = \implode('/', [ROOT, 'Cabin', $cabinName, 'Lens', 'common']); if (!\is_link($startLink)) {
/** * Locate the path that hosts the motif * * @param string $cabin * @param array $info * @return string[] */ protected function getMotifPath(string $cabin, array $info) : array { if (\file_exists(ROOT . '/Cabin/' . $cabin . '/config/motifs.json')) { $motifs = \Airship\loadJSON(ROOT . '/Cabin/' . $cabin . '/config/motifs.json'); foreach ($motifs as $path => $motifConfig) { if ($motifConfig['supplier'] === $info['supplier']) { if ($motifConfig['name'] === $info['name']) { return [$motifConfig['path'], ROOT . '/Motifs/' . $path]; } } } } return []; }
/** * Allows users to select which Motif to use * * @route my/preferences */ public function preferences() { if (!$this->isLoggedIn()) { \Airship\redirect($this->airship_cabin_prefix); } $prefs = $this->acct->getUserPreferences($this->getActiveUserId()); $cabins = []; $motifs = []; foreach ($this->getCabinNamespaces() as $cabin) { $cabins[] = $cabin; $filename = ROOT . '/tmp/cache/' . $cabin . '.motifs.json'; if (\file_exists($filename)) { $motifs[$cabin] = \Airship\loadJSON($filename); } else { $motifs[$cabin] = []; } } $post = $this->post(PreferencesFilter::fromConfig($cabins, $motifs)); if (!empty($post)) { if ($this->savePreferences($post['prefs'], $cabins, $motifs)) { \Airship\redirect($this->airship_cabin_prefix . '/my/preferences'); } } $this->lens('preferences', ['prefs' => $prefs, 'motifs' => $motifs]); }
/** * Get the channels * * @param string $name * @return Channel * @throws NoAPIResponse */ protected function getChannel(string $name) : Channel { if (empty(self::$channels)) { $config = \Airship\loadJSON(ROOT . '/config/channels.json'); foreach ($config as $chName => $chConfig) { self::$channels[$chName] = new Channel($this, $chName, $chConfig); } } if (isset(self::$channels[$name])) { return self::$channels[$name]; } throw new NoAPIResponse(\trk('errors.hail.no_channel_configured')); }
return $csrf->insertToken($lockTo); })); $twigEnv->addFunction(new Twig_SimpleFunction('cabin_url', function () { return '/'; })); $twigEnv->addFunction(new Twig_SimpleFunction('__', function (string $str = '') { // Not translating here. return $str; })); $twigEnv->addFunction(new Twig_SimpleFunction('get_loaded_extensions', function () { return \get_loaded_extensions(); })); $twigEnv->addGlobal('SERVER', $_SERVER); require_once ROOT . '/keys.php'; try { $step = \Airship\loadJSON(ROOT . '/tmp/installing.json'); if (empty($step)) { \file_put_contents(ROOT . '/tmp/installing.json', '[]'); $step = []; } } catch (FileNotFound $e) { \file_put_contents(ROOT . '/tmp/installing.json', '[]'); try { $step = \Airship\loadJSON(ROOT . '/tmp/installing.json'); } catch (FileNotFound $e) { die("Cannot create " . ROOT . '/tmp/installing.json'); } } require_once ROOT . "/Installer/symlinks.php"; $installer = new \Airship\Installer\Install($twigEnv, $step); $installer->currentStep();