Example #1
0
 /**
  * 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);
 }
Example #2
0
/**
 * 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';
    if (\file_exists($hpkpCacheFile) && \filesize($hpkpCacheFile) > 0) {
        $hpkp = HPKPBuilder::fromFile($hpkpCacheFile);
        $state->HPKP = $hpkp;
Example #3
0
 /**
  * Deletes a motif from a cabin's configuration
  *
  * @param string $cabin
  * @param string $key
  */
 protected function deleteMotifFromCabin(string $cabin, string $key)
 {
     $filename = ROOT . '/Cabin/' . $cabin . '/config/motifs.json';
     $motifs = \Airship\loadJSON($filename);
     if (isset($motifs[$key])) {
         unset($motifs[$key]);
         \Airship\saveJSON($filename, $motifs);
     }
 }
Example #4
0
 /**
  * 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;
 }
Example #5
0
 /**
  * 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);
 }
Example #6
0
 /**
  * @param array $motifs
  * @param array $post
  * @param string $cabin
  * @return bool
  */
 protected function updateMotifs(array $motifs, array $post, string $cabin) : bool
 {
     foreach ($motifs as $i => $motif) {
         $motifs[$i]['enabled'] = !empty($post['motifs_enabled']);
     }
     return \Airship\saveJSON(ROOT . '/Cabin/' . $cabin . '/config/motifs.json', $motifs);
 }
Example #7
0
 /**
  * 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();
 }
Example #8
0
    $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);
    }
}
Example #9
0
            // Expose common template snippets to the template loader:
            $startLink = \implode('/', [ROOT, 'Cabin', $cabinName, 'Lens', 'common']);
            if (!\is_link($startLink)) {
                \symlink(ROOT . '/common', $startLink);
            }
        } catch (Exception $ex) {
            $cabin['data'] = null;
        }
        if (empty($active_cabin) && $ap::isActiveCabinKey($key)) {
            if ($cabin['enabled']) {
                $active_cabin = $key;
            } else {
                $cabinDisabled = true;
            }
        }
        $cabins[$key] = $cabin;
    }
    if ($cabinDisabled) {
        \http_response_code(404);
        echo \file_get_contents(__DIR__ . '/error_pages/no-cabin.html');
        exit(1);
    }
    if (empty($active_cabin)) {
        $k = \array_keys($cabins);
        $active_cabin = \array_pop($k);
        unset($k);
    }
    $state->active_cabin = $active_cabin;
    $state->cabins = $cabins;
    \Airship\saveJSON(ROOT . '/tmp/cache/cabin_data.json', ['cabins' => $cabins]);
}
Example #10
0
                    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)) {
    $activeMotif = $userMotif['name'];
} elseif (isset($_settings['active-motif'])) {
    $activeMotif = $_settings['active-motif'];
}
if (isset($activeMotif)) {
    $lens->setBaseTemplate($activeMotif)->loadMotifCargo($activeMotif)->loadMotifConfig($activeMotif)->addGlobal('MOTIF', $state->motif_config);
}
Example #11
0
 /**
  * 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;
 }
Example #12
0
 /**
  * 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);
 }
Example #13
0
 /**
  * Update the universal gadgets
  *
  * @param array $gadgets
  * @param array $post
  * @return bool
  */
 protected function updateUniversalGadgets(array $gadgets, array $post) : bool
 {
     $sortedGadgets = [];
     foreach (\array_unique($post['gadget_order']) as $i => $index) {
         $gadgets[$index]['enabled'] = !empty($post['gadget_enabled'][$index]);
         $sortedGadgets[] = $gadgets[$index];
         unset($gadgets[$index]);
     }
     // Just in case any were omitted
     foreach ($gadgets as $gadget) {
         $gadget['enabled'] = false;
         $sortedGadgets[] = $gadget;
     }
     return \Airship\saveJSON(ROOT . '/config/gadgets.json', $sortedGadgets);
 }
Example #14
0
 /**
  * Get the data for the cabins.json file
  * 
  * This is in a separate method so it can be unit tested
  * @param \Twig_Environment $twig
  * @return string
  */
 protected function finalConfigCabins(\Twig_Environment $twig) : string
 {
     $cabins = [];
     foreach ($this->data['cabins'] as $name => $conf) {
         $cabins[$conf['path']] = ['https' => !empty($conf['https']), 'enabled' => true, 'language' => $conf['lang'] ?? 'en-us', 'canon_url' => $conf['canon_url'], 'name' => $name];
         \Airship\saveJSON(ROOT . '/Cabin/' . $name . '/config/config.json', $this->data['config_extra'][$name] ?? []);
         \Airship\saveJSON(ROOT . '/Cabin/' . $name . '/config/twig_vars.json', $this->data['twig_vars'][$name] ?? []);
     }
     return $twig->render('cabins.twig', ['cabins' => $cabins]);
 }