/** * 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; } } }
/** * 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; }
/** * 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]); }
/** * 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; }
/** * 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; }
/** * 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(); } }
/** * 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; }
/** * 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); } }
/** * 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); }
/** * 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]; }
/** * 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] ?? []; }
/** * 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; }
/** * 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; }
/** * 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)]; }
/** * 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(); }
/** * 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]; }
<?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); }
/** * 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; }
/** * 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); }
/** * 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); }); } }
/** * 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']; }
/** * 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'); }
/** * 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; }
/** * 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; }