Esempio n. 1
0
 /**
  * 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();
 }
Esempio n. 2
0
 /**
  * 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;
 }
Esempio n. 3
0
 /**
  * @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')]);
 }
Esempio n. 4
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);
 }
Esempio n. 5
0
 /**
  * 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;
 }
Esempio n. 6
0
 *
 * @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';
Esempio n. 7
0
 /**
  * @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');
     }
 }
Esempio n. 8
0
/**
 * 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;
}
Esempio n. 9
0
 /**
  * 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));
 }
Esempio n. 10
0
 /**
  * @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;
 }
Esempio n. 11
0
// 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']));
Esempio n. 12
0
 /**
  * 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
 }
Esempio n. 13
0
<?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'];
                }
Esempio n. 14
0
<?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
}
Esempio n. 15
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);
 }
Esempio n. 16
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);
    }
}
Esempio n. 17
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();
 }
Esempio n. 18
0
                        // 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)) {
Esempio n. 19
0
<?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);
Esempio n. 20
0
 /**
  * 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;
 }
Esempio n. 21
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);
 }
Esempio n. 22
0
 /**
  * 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();
 }
Esempio n. 23
0
 /**
  * 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;
 }
Esempio n. 24
0
 /**
  * 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;
 }
Esempio n. 25
0
 /**
  * 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;
 }
Esempio n. 26
0
            $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)) {
Esempio n. 27
0
 /**
  * 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 [];
 }
Esempio n. 28
0
 /**
  * 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]);
 }
Esempio n. 29
0
 /**
  * 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'));
 }
Esempio n. 30
0
    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();