/**
  * Default action in default controller
  */
 public function action_index()
 {
     // get acl
     $acl = Acl::instance();
     // get first allowed module
     // get modules
     $modules = Settings::factory('modules')->as_array();
     $modules = array_keys($modules);
     $module = State::instance()->get('active.module');
     if ($module !== FALSE && $module !== 'Default') {
         if ($acl->allowed($module, 'access', FALSE, $this->_website) === TRUE) {
             $url = URL::to($module, array('website' => $this->_website));
             $this->redirect($url);
             exit;
         }
     }
     // find the first allowed module & redirect
     foreach ($modules as $module) {
         if ($acl->allowed($module, 'access', FALSE, $this->_website) === TRUE) {
             $url = URL::to($module, array('website' => $this->_website));
             $this->redirect($url);
             exit;
         }
     }
 }
Exemple #2
0
 /**
  * Perform a permissions check
  *
  * @param string $action action label (e.g. 'read')
  * @param string $context_path context regex (in perm_contexts)
  * @param string $cabin (defaults to current cabin)
  * @param integer $user_id (defaults to current user)
  * @return boolean
  */
 public function can(string $action, string $context_path = '', string $cabin = \CABIN_NAME, int $user_id = 0) : bool
 {
     $state = State::instance();
     if (empty($cabin)) {
         $cabin = \CABIN_NAME;
     }
     // If you don't specify the user ID to check, it will use the current
     // user ID instead, by default.
     if (empty($user_id)) {
         if (!empty($_SESSION['userid'])) {
             $user_id = (int) $_SESSION['userid'];
         }
     }
     // If you're a super-user, the answer is "yes, yes you can".
     if ($this->isSuperUser($user_id)) {
         return true;
     }
     $allowed = false;
     $failed_one = false;
     // Get all applicable contexts
     $contexts = self::getOverlap($context_path, $cabin);
     if (empty($contexts)) {
         // Sane default: In the absence of permissions, return false
         return false;
     }
     if ($user_id > 0) {
         // You need to be allowed in every relevant context.
         foreach ($contexts as $c_id) {
             if (self::checkUser($action, $c_id, $user_id) || self::checkUsersGroups($action, $c_id, $user_id)) {
                 $allowed = true;
             } else {
                 $failed_one = true;
             }
         }
     } else {
         if (!$state->universal['guest_groups']) {
             return false;
         }
         // Guests can be assigned to groups. This fails closed if they aren't given any.
         foreach ($contexts as $c_id) {
             $ctx_res = false;
             foreach ($state->universal['guest_groups'] as $grp) {
                 if (self::checkGroup($action, $c_id, $grp)) {
                     $ctx_res = true;
                 }
             }
             if ($ctx_res) {
                 $allowed = true;
             } else {
                 $failed_one = true;
             }
         }
     }
     // We return true if we were allowed at least once and we did not fail
     // in one of the overlapping contexts
     return $allowed && !$failed_one;
 }
Exemple #3
0
 /**
  * This function is called after the dependencies have been injected by
  * AutoPilot. Think of it as a user-land constructor.
  */
 public function airshipLand()
 {
     parent::airshipLand();
     $state = State::instance();
     $cabin_names = [];
     foreach ($state->cabins as $c) {
         $cabin_names[] = $c['name'];
     }
     $this->airship_lens_object->store('state', ['cabins' => $state->cabins, 'cabin_names' => $cabin_names, 'manifest' => $state->manifest]);
 }
Exemple #4
0
 /**
  * AirBrake constructor.
  * @param Database|null $db
  * @param array $config
  */
 public function __construct(Database $db = null, array $config = [])
 {
     if (!$db) {
         $db = \Airship\get_database();
     }
     if (empty($config)) {
         $state = State::instance();
         $config = $state->universal['rate-limiting'];
     }
     $this->db = $db;
     $this->config = $config;
 }
Exemple #5
0
 /**
  * Given a URL, return the cabin name that applies to it;
  * otherwise, throw a CabinNotFound exception.
  *
  * @param string $url
  * @return string
  * @throws CabinNotFound
  */
 public function getCabinNameFromURL(string $url) : string
 {
     $state = State::instance();
     /**
      * @var AutoPilot
      */
     $ap = $state->autoPilot;
     $cabin = $ap->testCabinForUrl($url);
     if (empty($cabin)) {
         throw new CabinNotFound();
     }
     return $cabin;
 }
Exemple #6
0
 /**
  * Installer constructor.
  *
  * @param Hail|null $hail
  * @param string $supplier
  * @param string $package
  */
 public function __construct(Hail $hail = null, string $supplier = '', string $package = '')
 {
     $config = State::instance();
     if (empty($hail)) {
         $this->hail = $config->hail;
     } else {
         $this->hail = $hail;
     }
     $this->supplier = $this->getSupplierDontCache($supplier);
     $this->package = $package;
     if (!self::$continuumLogger) {
         self::$continuumLogger = new Log();
     }
 }
Exemple #7
0
 /**
  * SharedMemory constructor
  *.
  * @param Key|null $cacheKey
  * @param AuthenticationKey|null $authKey
  * @param string $personalization
  */
 public function __construct(Key $cacheKey = null, AuthenticationKey $authKey = null, string $personalization = '')
 {
     if (!$cacheKey) {
         $state = State::instance();
         $cacheKey = $state->keyring['cache.hash_key'];
     }
     // We need a short hash key:
     $this->cacheKeyL = CryptoUtil::safeSubstr($cacheKey->getRawKeyMaterial(), 0, \Sodium\CRYPTO_SHORTHASH_KEYBYTES);
     $this->cacheKeyR = CryptoUtil::safeSubstr($cacheKey->getRawKeyMaterial(), \Sodium\CRYPTO_SHORTHASH_KEYBYTES, \Sodium\CRYPTO_SHORTHASH_KEYBYTES);
     if ($authKey) {
         $this->authKey = $authKey;
     }
     $this->personalization = $personalization;
 }
Exemple #8
0
 /**
  * Process automatic updates:
  *
  * 1. Check if a new update is available.
  * 2. Download the upload file, store in a temporary file.
  * 3. Verify the signature (via Halite).
  * 4. Verify the update is recorded in Keyggdrasil.
  * 5. If all is well, run the update script.
  */
 public function autoUpdate()
 {
     $state = State::instance();
     try {
         /**
          * @var UpdateInfo[]
          */
         $updateInfoArray = $this->updateCheck($state->universal['airship']['trusted-supplier'], $this->name, \AIRSHIP_VERSION, 'airship_version');
         /**
          * Let's iterate through every possible available update,
          * until we find the desired version.
          */
         foreach ($updateInfoArray as $updateInfo) {
             if (!$this->checkVersionSettings($updateInfo, \AIRSHIP_VERSION)) {
                 // This is not the update we are looking for.
                 $this->log('Skipping update', LogLevel::DEBUG, ['info' => $updateInfo->getResponse(), 'new_version' => $updateInfo->getVersion(), 'current_version' => \AIRSHIP_VERSION]);
                 continue;
             }
             /**
              * @var UpdateFile
              */
             $updateFile = $this->downloadUpdateFile($updateInfo, 'airship_download');
             if ($this->bypassSecurityAndJustInstall) {
                 // I'm sorry, Dave. I'm afraid I can't do that.
                 $this->log('Core update verification cannot be bypassed', LogLevel::ERROR);
                 self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update - security bypass ignored.', $this->getLogContext($updateInfo, $updateFile));
             }
             /**
              * Don't proceed unless we've verified the signatures
              */
             if ($this->verifyUpdateSignature($updateInfo, $updateFile)) {
                 if ($this->checkKeyggdrasil($updateInfo, $updateFile)) {
                     $this->install($updateInfo, $updateFile);
                 } else {
                     $this->log('Keyggdrasil check failed for Airship core update', LogLevel::ALERT);
                     self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update failed -- checksum not registered in Keyggdrasil', $this->getLogContext($updateInfo, $updateFile));
                 }
             } else {
                 $this->log('Invalid signature for this Airship core update', LogLevel::ALERT);
                 self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update failed -- invalid signature', $this->getLogContext($updateInfo, $updateFile));
             }
         }
     } catch (NoAPIResponse $ex) {
         // We should log this.
         $this->log('Automatic update failure: NO API Response.', LogLevel::ERROR, \Airship\throwableToArray($ex));
         self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update failed -- no API Response', ['action' => 'UPDATE', 'name' => $this->name, 'supplier' => $this->supplier->getName(), 'type' => $this->type]);
     }
 }
 /**
  * after handler
  */
 public function after()
 {
     // store active module in State
     State::instance()->set('active.module', $this->_controller);
 }
 /**
  * update
  */
 public function action_update()
 {
     // get id
     $id = $this->param('id');
     // create new model
     $model = ORM::factory($this->_settings->get('model'), $id);
     // add item to navigation
     Viewer::instance('Navigation')->item($model);
     // create form
     $form = Form::factory($this->_settings->get('form'));
     // add request to form
     $form->request($this->request);
     // add text to form
     $form->text(Text::instance());
     // add alias settings to form
     $form->alias($this->_settings->get('alias.global') ? 'multiple' : ($this->_settings->get('alias.module') ? 'single' : FALSE));
     // add model to form
     $form->model($model);
     // get viewport
     $viewport = $this->request->param('viewport');
     // add urls
     if ($viewport === 'item') {
         // urls when directly updating in a dialog
         $form->urls(array('submit' => URL::to($this->request->controller() . '@update:' . $id, array('query' => 'after=update')), 'submit_back' => URL::to($this->request->controller() . '@update:' . $id, array('query' => 'after=close')), 'back' => URL::to($this->request->controller() . '@close'), 'preview' => URL::to($this->request->controller() . '@preview:' . $id)));
     } else {
         // default urls
         $url_back = State::instance()->get('url.back', FALSE);
         State::instance()->set('url.back', FALSE);
         $form->urls(array('submit' => URL::to($this->request->controller() . '@update:' . $id, array('query' => 'after=update')), 'submit_back' => URL::to($this->request->controller() . '@update:' . $id), 'back' => $url_back ? $url_back : URL::to($this->request->controller()), 'preview' => URL::to($this->request->controller() . '@preview:' . $id)));
     }
     // raise event
     Event::raise($this, Event::AFTER_UPDATE_FORM, array('form' => $form, 'model' => $model));
     // do the action
     if ($this->update($model, $form)) {
         // get after
         if ($this->request->query('after') === 'update') {
             $params = array('controller' => $this->request->controller(), 'action' => 'update', 'id' => $id);
         } elseif ($this->request->query('after') === 'close') {
             $params = array('controller' => $this->request->controller(), 'action' => 'close', 'id' => $id);
         } else {
             $params = array();
         }
         //redirect
         $this->redirect_done('updated', $params);
     }
 }
Exemple #11
0
 /**
  * Is this cabin part of the Airship core? (They don't
  * get automatically updated separate from the core.)
  *
  * @return bool
  */
 protected function isAirshipSpecialCabin() : bool
 {
     $state = State::instance();
     return $this->supplier->getName() === $state->universal['airship']['trusted-supplier'] && \in_array($this->name, self::AIRSHIP_SPECIAL_CABINS);
 }
Exemple #12
0
 /**
  * Discover a new notary, grab its public key, channels, and URL
  *
  * @param string $url
  * @return array
  */
 protected function notaryDiscovery(string $url) : array
 {
     $state = State::instance();
     if (IDE_HACKS) {
         $state->hail = new Hail(new Client());
     }
     $body = $state->hail->getReturnBody($url);
     $pos = \strpos($body, '<meta name="airship-notary" content="');
     if ($pos === false) {
         // Notary not enabled:
         return [];
     }
     $body = Util::subString($body, $pos + 37);
     $end = \strpos($body, '"');
     if (!$end) {
         // Invalid
         return [];
     }
     $tag = \explode('; ', Util::subString($body, 0, $end));
     $channel = null;
     $notary_url = null;
     foreach ($tag as $t) {
         list($k, $v) = \explode('=', $t);
         if ($k === 'channel') {
             $channel = $v;
         } elseif ($k === 'url') {
             $notary_url = $v;
         }
     }
     return ['public_key' => $tag[0], 'channel' => $channel, 'url' => $notary_url];
 }
Exemple #13
0
/**
 * Get the user's selected Motif
 *
 * @param int|null $userId
 * @param string $cabin
 * @return array
 */
function user_motif(int $userId = null, string $cabin = \CABIN_NAME) : array
{
    static $userCache = [];
    $state = State::instance();
    if (\count($state->motifs) === 0) {
        return [];
    }
    if (empty($userId)) {
        $userId = \Airship\LensFunctions\userid();
        if (empty($userId)) {
            $k = \array_keys($state->motifs)[0];
            return $state->motifs[$k] ?? [];
        }
    }
    // Did we cache these preferences?
    if (isset($userCache[$userId])) {
        return $state->motifs[$userCache[$userId]];
    }
    $db = \Airship\get_database();
    $userPrefs = $db->cell('SELECT preferences FROM airship_user_preferences WHERE userid = ?', $userId);
    if (empty($userPrefs)) {
        // Default
        $k = \array_keys($state->motifs)[0];
        $userCache[$userId] = $k;
        return $state->motifs[$k] ?? [];
    }
    $userPrefs = \Airship\parseJSON($userPrefs, true);
    if (isset($userPrefs['motif'][$cabin])) {
        $split = \explode('/', $userPrefs['motif'][$cabin]);
        foreach ($state->motifs as $k => $motif) {
            if (empty($motif['config'])) {
                continue;
            }
            if ($motif['supplier'] === $split[0] && $motif['name'] === $split[1]) {
                // We've found a match:
                $userCache[$userId] = $k;
                return $state->motifs[$k];
            }
        }
    }
    // When all else fails, go with the first one
    $k = \array_keys($state->motifs)[0];
    $userCache[$userId] = $k;
    return $state->motifs[$k] ?? [];
}
Exemple #14
0
 /**
  * Generate a unique random public ID for this user, which is distinct from the username they use to log in.
  *
  * @return string
  */
 protected function generateUniqueId() : string
 {
     $unique = '';
     $query = 'SELECT count(*) FROM airship_users WHERE uniqueid = ?';
     do {
         if (!empty($unique)) {
             // This will probably never be executed. It will be a nice easter egg if it ever does.
             $state = State::instance();
             $state->logger->log(LogLevel::ALERT, "A unique user ID collision occurred. This should never happen. (There are 2^192 possible values," . "which has approximately a 50% chance of a single collision occurring after 2^96 users," . "and the database can only hold 2^64). This means you're either extremely lucky or your CSPRNG " . "is broken. We hope it's luck. Airship is clever enough to try again and not fail " . "(so don't worry), but we wanted to make sure you were aware.", ['colliding_random_id' => $unique]);
         }
         $unique = \Airship\uniqueId();
     } while ($this->db->exists($query, $unique));
     return $unique;
 }
Exemple #15
0
 /**
  * Find probable collisions between patterns and cabin names, as well as hard-coded paths
  * in the current cabin. It does NOT look for collisions in custom pages, nor in page collisions
  * in foreign Cabins (outside of the Cabin itself).
  *
  * @param string $uri
  * @param string $cabin
  * @return bool
  * @throws \Airship\Alerts\GearNotFound
  * @throws \TypeError
  */
 protected function detectCollisions(string $uri, string $cabin) : bool
 {
     $state = State::instance();
     $ap = Gears::getName('AutoPilot');
     if (!$ap instanceof AutoPilot) {
         throw new \TypeError(\__('AutoPilot Blueprint'));
     }
     $nop = [];
     foreach ($state->cabins as $pattern => $cab) {
         if ($cab === $cabin) {
             // Let's check each existing route in the current cabin for a collision
             foreach ($cab['data']['routes'] as $route => $landing) {
                 $test = $ap::testLanding($ap::$patternPrefix . $route . '$', $uri, $nop, true);
                 if ($test) {
                     return true;
                 }
             }
         } else {
             // Let's check each cabin route for a pattern
             $test = $ap::testLanding($ap::$patternPrefix . $pattern, $uri, $nop, true);
             if ($test) {
                 return true;
             }
         }
     }
     return \preg_match('#^(static|js|img|fonts|css)/#', $uri) === 0;
 }
Exemple #16
0
 /**
  * Get a random URL
  *
  * @return string
  */
 public function getURL() : string
 {
     $state = State::instance();
     $candidates = [];
     if ($state->universal['tor-only']) {
         // Prioritize Tor Hidden Services
         foreach ($this->urls as $url) {
             if (\Airship\isOnionUrl($url)) {
                 $candidates[] = $url;
             }
         }
         // If we had any .onions, we will only use those.
         // Otherwise, use non-Tor URLs over Tor.
         if (empty($candidates)) {
             $candidates = $this->urls;
         }
     } else {
         $candidates = $this->urls;
     }
     $max = \count($candidates) - 1;
     return $candidates[\random_int(0, $max)];
 }
Exemple #17
0
 /**
  * Finalize the install process
  * 
  * @param array $post
  */
 protected function finalize(array $post = [])
 {
     $state = State::instance();
     $this->data['admin']['username'] = $post['username'];
     if (!empty($post['passphrase'])) {
         // Password was changed:
         $this->data['admin']['passphrase'] = Password::hash($post['passphrase'], $state->keyring['auth.password_key']);
     }
     $this->data['cabins'] = $post['cabin'];
     $this->data['config'] = $post['config'];
     $this->data['database'] = $post['database'];
     $this->finalConfiguration();
     $this->finalDatabaseSetup();
     $this->finalProcessAdminAccount();
     $this->finalShutdown();
 }
Exemple #18
0
 /**
  * Process an upload. Either it returns an array with useful data,
  * OR it throws an UploadError
  *
  * @param int|null $directoryId
  * @param string $cabin
  * @param array $file
  * @param array $attribution Who uploaded it?
  * @return array
  * @throws UploadError
  */
 public function processUpload($directoryId = null, string $cabin = '', array $file = [], array $attribution = []) : array
 {
     // First step: Validate our file data
     switch ($file['error']) {
         case UPLOAD_ERR_INI_SIZE:
         case UPLOAD_ERR_FORM_SIZE:
             throw new UploadError(\__('File is too large'));
         case UPLOAD_ERR_PARTIAL:
             throw new UploadError(\__('Partial file received'));
         case UPLOAD_ERR_NO_TMP_DIR:
             throw new UploadError(\__('Temporary directory does not exist'));
         case UPLOAD_ERR_CANT_WRITE:
             throw new UploadError(\__('Cannot write to temporary directory'));
         case UPLOAD_ERR_OK:
             // Continue
             break;
     }
     if ($file['name'] === '..') {
         throw new UploadError(\__('Invalid file name'));
     }
     if (\preg_match('#([^/]+)\\.([a-zA-Z0-9]+)$#', $file['name'], $matches)) {
         $name = $matches[1];
         $ext = $matches[2];
     } elseif (\preg_match('#([^/\\.]+)$#', $file['name'], $matches)) {
         $name = $matches[1];
         $ext = 'txt';
     } else {
         throw new UploadError(\__('Invalid file name'));
     }
     // Actually upload the file.
     $destination = $this->moveUploadedFile($file['tmp_name']);
     $fullpath = AIRSHIP_UPLOADS . $destination;
     // Get the MIME type and checksum
     $type = $this->getMimeType($fullpath);
     $state = State::instance();
     $checksum = HaliteFile::checksum($fullpath, $state->keyring['cache.hash_key']);
     // Begin transaction
     $this->db->beginTransaction();
     // Get a unique file name
     $filename = $this->getUniqueFileName($name, $ext, $directoryId, $cabin);
     // Insert the new record
     $store = ['filename' => $filename, 'type' => $type, 'realname' => $destination, 'checksum' => $checksum, 'uploaded_by' => (int) ($attribution['uploaded_by'] ?? $this->getActiveUserId())];
     if ($directoryId) {
         $store['directory'] = (int) $directoryId;
     } else {
         $store['cabin'] = (string) $cabin;
     }
     if (!empty($attribution['author'])) {
         $store['author'] = $attribution['author'];
     }
     $newId = $this->db->insertGet('airship_files', $store, 'fileid');
     // Did our INSERT query fail?
     if (!$this->db->commit()) {
         // Clean up orphaned file, it was a database error.
         \unlink($fullpath);
         $this->db->rollBack();
         throw new UploadError(\__('A database error occurred trying to save %s', 'default', $destination));
     }
     // Return metadata
     return ['fileid' => $newId, 'name' => $filename, 'type' => $type, 'csum' => $checksum];
 }
Exemple #19
0
<?php

declare (strict_types=1);
use Airship\Engine\{AutoPilot, State};
use ParagonIE\Cookie\{Cookie, Session};
/**
 * @global State $state
 */
// Start the session
if (!Session::id()) {
    if (!isset($state)) {
        $state = State::instance();
    }
    $session_config = ['use_strict_mode' => true, 'entropy_length' => 32, 'cookie_httponly' => true, 'cookie_secure' => AutoPilot::isHTTPSConnection()];
    if (isset($state->universal['session_config'])) {
        $session_config = $state->universal['session_config'] + $session_config;
        if (isset($session_config['cookie_domain'])) {
            if ($session_config['cookie_domain'] === '*' || \trim($session_config['cookie_domain']) === '') {
                unset($session_config['cookie_domain']);
            }
        }
    }
    if (\PHP_VERSION_ID >= 70100) {
        // Forward compatibility.
        unset($session_config['entropy_length']);
    }
    Session::start(Cookie::SAME_SITE_RESTRICTION_STRICT, $session_config);
}
if (empty($_SESSION['created_canary'])) {
    // We haven't seen this session ID before
    $_SESSION = [];
 /**
  * create the top navigation
  */
 public function action_top()
 {
     // get acl
     $acl = Acl::instance();
     // get websites
     $websites = array();
     foreach (Kohana::$config->load('websites')->websites as $key => $website) {
         if ($acl->allowed('Environment', 'access', FALSE, $key)) {
             $websites[$key] = $website;
         }
     }
     // get modules
     $modules = Settings::factory('modules')->as_array();
     // get settings
     $settings = Settings::factory('navigation', array('settings' . DIRECTORY_SEPARATOR . Website::instance()->id() . DIRECTORY_SEPARATOR, 'settings'));
     // get navigation
     $navigation = $settings->get('menu');
     // get current controller
     $controller = Request::initial()->controller();
     // filter out allowed modules
     $allowedModules = array();
     foreach ($modules as $module => $data) {
         if ($acl->allowed($module, 'access')) {
             $allowedModules[$module] = TRUE;
         }
     }
     // create a nested array with sections => modules
     // catch active section
     $sections = array();
     $sectionActive = FALSE;
     foreach ($navigation as $section => $modules) {
         foreach ($modules as $module) {
             if (isset($allowedModules[$module])) {
                 // section has a allowed module, so include it
                 if (!isset($sections[$section])) {
                     $sections[$section] = array();
                 }
                 // add module to section
                 $sections[$section][] = $module;
                 // check if this is the active section
                 if ($module === $controller) {
                     $sectionActive = $section;
                 }
             }
         }
     }
     // store active section
     if ($sectionActive) {
         // get current website
         State::instance()->set('active.section', $sectionActive);
     }
     // check if frontendlink should be shown
     if ($settings->get('frontend')) {
         $frontend = URL::to('', array('website' => Website::instance()->id(), 'query' => 'edit=1'), 'frontend', Website::instance());
     } else {
         $frontend = FALSE;
     }
     // create view
     $view = View::factory('navigation/top', array('acl' => $acl, 'websites' => $websites, 'sections' => $sections, 'active' => $sectionActive, 'website' => Website::instance()->id(), 'frontend' => $frontend));
     // render view
     $this->response->body($view->render());
 }
 /**
  * Translation (lookup table based on a key)
  * 
  * @param string $key
  * @param mixed ...$params
  * @return string
  */
 function trk(string $key, ...$params) : string
 {
     static $gear = null;
     if ($gear === null) {
         $gear = Gears::get('Translation');
     }
     if (!empty($params)) {
         \array_walk($params, '\\Airship\\LensFunctions\\get_purified');
     }
     $state = State::instance();
     return $gear->lookup($key, (string) ($state->lang ?? 'en-us'), ...$params);
 }
Exemple #22
0
 /**
  * Let's do an automatic login
  *
  * @param string $token
  * @param string $uid_idx
  * @param string $token_idx
  * @return bool
  * @throws LongTermAuthAlert (only in debug mode)
  * @throws \TypeError
  */
 protected function doAutoLogin(string $token, string $uid_idx, string $token_idx) : bool
 {
     if (!$this->airship_auth instanceof Authentication) {
         $this->tightenSecurityBolt();
     }
     $state = State::instance();
     try {
         $userId = $this->airship_auth->loginByToken($token);
         \Sodium\memzero($token);
         if (!$this->verifySessionCanary($userId, false)) {
             return false;
         }
         // Regenerate session ID:
         Session::regenerate(true);
         // Set session variable
         $_SESSION[$uid_idx] = $userId;
         $autoPilot = Gears::getName('AutoPilot');
         if (IDE_HACKS) {
             // We're using getName(), this is just to fool IDEs.
             $autoPilot = new AutoPilot();
         }
         $httpsOnly = (bool) $autoPilot::isHTTPSConnection();
         // Rotate the authentication token:
         Cookie::setcookie($token_idx, Symmetric::encrypt($this->airship_auth->rotateToken($token, $userId), $state->keyring['cookie.encrypt_key']), \time() + ($state->universal['long-term-auth-expire'] ?? self::DEFAULT_LONGTERMAUTH_EXPIRE), '/', '', $httpsOnly ?? false, true);
         return true;
     } catch (LongTermAuthAlert $e) {
         $state = State::instance();
         // Let's wipe our long-term authentication cookies
         Cookie::setcookie($token_idx, null, 0, '/', '', $httpsOnly ?? false, true);
         // Let's log this incident
         if (\property_exists($this, 'log')) {
             $this->log($e->getMessage(), LogLevel::CRITICAL, ['exception' => \Airship\throwableToArray($e)]);
         } else {
             $state->logger->log(LogLevel::CRITICAL, $e->getMessage(), ['exception' => \Airship\throwableToArray($e)]);
         }
         // In debug mode, re-throw the exception:
         if ($state->universal['debug']) {
             throw $e;
         }
     }
     return false;
 }
Exemple #23
0
/**
 * Get a ReCAPTCHA object configured to use
 *
 * @param string $secretKey
 * @param array $opts
 * @return ReCaptcha
 */
function getReCaptcha(string $secretKey, array $opts = []) : ReCaptcha
{
    $state = State::instance();
    // Merge arrays:
    $opts = $opts + $state->universal['guzzle'];
    // Forcefully route this over Tor
    if ($state->universal['tor-only']) {
        $opts[CURLOPT_PROXY] = 'http://127.0.0.1:9050/';
        $opts[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5;
    }
    $curlPost = new CurlPost(null, $opts);
    return new ReCaptcha($secretKey, $curlPost);
}
Exemple #24
0
 /**
  * This propagates the new update through the network.
  */
 protected function notifyPeersOfNewUpdate()
 {
     $state = State::instance();
     if (IDE_HACKS) {
         $state->hail = new Hail(new Client());
     }
     $resp = [];
     $peers = \Airship\loadJSON(ROOT . '/config/channel_peers/' . $this->channel . '.json');
     foreach ($peers as $peer) {
         foreach ($peer['urls'] as $url) {
             $resp[] = $state->hail->getAsync($url, ['challenge' => Base64UrlSafe::encode(\random_bytes(21))]);
         }
     }
     foreach ($resp as $r) {
         $r->then(function (ResponseInterface $response) {
             $body = (string) $response->getBody();
             $context = \json_decode(Binary::safeSubstr($body, 89));
             $this->log('Peer notified of channel update', LogLevel::INFO, $context);
         });
     }
 }
Exemple #25
0
 /**
  * Get the updated metadata for a particular package.
  *
  * @param string $type
  * @param string $supplier
  * @param string $pkg
  * @return array
  */
 protected function getPackageMetadata(string $type, string $supplier, string $pkg) : array
 {
     $state = State::instance();
     if (IDE_HACKS) {
         $state->hail = new Hail(new Client());
     }
     $channels = \Airship\loadJSON(ROOT . "/config/channels.json");
     $ch = $state->universal['airship']['trusted-supplier'] ?? 'paragonie';
     if (empty($channels[$ch])) {
         return [];
     }
     $publicKey = new SignaturePublicKey(\Sodium\hex2bin($channels[$ch]['publickey']));
     foreach ($channels[$ch]['urls'] as $url) {
         try {
             $response = $state->hail->postSignedJSON($url, $publicKey, ['type' => $type, 'supplier' => $supplier, 'name' => $pkg]);
         } catch (NoAPIResponse $ex) {
             // Continue
         }
     }
     if (empty($response)) {
         return [];
     }
     if ($response['status'] !== 'success') {
         return [];
     }
     return $response['packageMetadata'];
 }
Exemple #26
0
 /**
  * If the token is valid, log in as the user.
  *
  * @param string $token
  */
 protected function processRecoveryToken(string $token)
 {
     if (Util::stringLength($token) < UserAccounts::RECOVERY_CHAR_LENGTH) {
         \Airship\redirect($this->airship_cabin_prefix . '/login');
     }
     $selector = Util::subString($token, 0, 32);
     $validator = Util::subString($token, 32);
     $ttl = (int) $this->config('password-reset.ttl');
     if (empty($ttl)) {
         \Airship\redirect($this->airship_cabin_prefix . '/login');
     }
     $recoveryInfo = $this->acct->getRecoveryData($selector, $ttl);
     if (empty($recoveryInfo)) {
         \Airship\redirect($this->airship_cabin_prefix . '/login');
     }
     $state = State::instance();
     if (Symmetric::verify($validator . $recoveryInfo['userid'], $state->keyring['auth.recovery_key'], $recoveryInfo['hashedtoken'])) {
         $_SESSION['userid'] = (int) $recoveryInfo['userid'];
         $_SESSION['session_canary'] = $this->acct->createSessionCanary($recoveryInfo['userid']);
         $this->acct->deleteRecoveryToken($selector);
         \Airship\redirect($this->airship_cabin_prefix . '/my/account');
     }
     \Airship\redirect($this->airship_cabin_prefix . '/login');
 }
Exemple #27
0
 /**
  * Should this automatic update be permitted?
  *
  * @param UpdateInfo $info
  * @param string $currentVersion
  * @return bool
  */
 protected function checkVersionSettings(UpdateInfo $info, string $currentVersion) : bool
 {
     $state = State::instance();
     $nextVersion = $info->getVersion();
     $version = new Version($currentVersion);
     // If this isn't an upgrade at all, don't apply it.
     if (!$version->isUpgrade($nextVersion)) {
         return false;
     }
     if ($version->isMajorUpgrade($nextVersion)) {
         return !empty($state->universal['auto-update']['major']);
     }
     if ($version->isMinorUpgrade($nextVersion)) {
         return !empty($state->universal['auto-update']['minor']);
     }
     if ($version->isPatchUpgrade($nextVersion)) {
         return !empty($state->universal['auto-update']['patch']);
     }
     return false;
 }
Exemple #28
0
 /**
  * Get a relative BLAKE2b hash of an input. Formatted as two lookup
  * directories followed by a cache entry. 'hh/hh/hhhhhhhh...'
  *
  * @param string $preHash The cache identifier (will be hashed)
  * @param bool $asString Return a string?
  * @return string|array
  * @throws InvalidType
  */
 public static function getRelativeHash(string $preHash, bool $asString = false)
 {
     $state = State::instance();
     $cacheKey = $state->keyring['cache.hash_key'];
     if (!$cacheKey instanceof Key) {
         throw new InvalidType(\trk('errors.type.wrong_class', '\\ParagonIE\\Halite\\Key'));
     }
     // We use a keyed hash, with a distinct key per Airship deployment to
     // make collisions unlikely,
     $hash = \Sodium\crypto_generichash($preHash, $cacheKey->getRawKeyMaterial(), self::HASH_SIZE);
     $relHash = [\Sodium\bin2hex($hash[0]), \Sodium\bin2hex($hash[1]), \Sodium\bin2hex(Util::subString($hash, 2))];
     if ($asString) {
         return \implode(DIRECTORY_SEPARATOR, $relHash);
     }
     return $relHash;
 }