Пример #1
0
<?php

declare (strict_types=1);
use Airship\Engine\LedgerStorage\{DBStore, FileStore};
use Airship\Engine\{Gears, State};
use ParagonIE\ConstantTime\Binary;
/**
 * Configure the application event logger here
 */
$log_setup_closure = function () {
    $state = State::instance();
    $loggerClass = Gears::getName('Ledger');
    $args = [];
    /**
     * Here we build our logger storage class
     */
    switch ($state->universal['ledger']['driver']) {
        case 'file':
            $path = $state->universal['ledger']['path'];
            if (Binary::safeStrlen($path) >= 2) {
                if ($path[0] === '~' && $path[1] === '/') {
                    $path = ROOT . '/' . Binary::safeSubstr($path, 2);
                }
            }
            $storage = new FileStore($path, $state->universal['ledger']['file-format'] ?? FileStore::FILE_FORMAT, $state->universal['ledger']['time-format'] ?? FileStore::TIME_FORMAT);
            break;
        case 'database':
            $path = $state->universal['ledger']['connection'];
            try {
                $storage = new DBStore($path, $state->universal['ledger']['table'] ?? DBStore::DEFAULT_TABLE);
            } catch (\Throwable $ex) {
Пример #2
0
 /**
  * Handle user authentication
  *
  * @param array $post
  */
 protected function processLogin(array $post = [])
 {
     $state = State::instance();
     if (empty($post['username']) || empty($post['passphrase'])) {
         $this->lens('login', ['post_response' => ['message' => \__('Please fill out the form entirely'), 'status' => 'error']]);
     }
     $airBrake = Gears::get('AirBrake');
     if (IDE_HACKS) {
         $airBrake = new AirBrake();
     }
     if ($airBrake->failFast($post['username'], $_SERVER['REMOTE_ADDR'])) {
         $this->lens('login', ['post_response' => ['message' => \__('You are doing that too fast. Please wait a few seconds and try again.'), 'status' => 'error']]);
     } elseif (!$airBrake->getFastExit()) {
         $delay = $airBrake->getDelay($post['username'], $_SERVER['REMOTE_ADDR']);
         if ($delay > 0) {
             \usleep($delay * 1000);
         }
     }
     try {
         $userID = $this->airship_auth->login($post['username'], new HiddenString($post['passphrase']));
     } catch (InvalidMessage $e) {
         $this->log('InvalidMessage Exception on Login; probable cause: password column was corrupted', LogLevel::CRITICAL, ['exception' => \Airship\throwableToArray($e)]);
         $this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
     }
     if (!empty($userID)) {
         $userID = (int) $userID;
         $user = $this->acct->getUserAccount($userID);
         if ($user['enable_2factor']) {
             if (empty($post['two_factor'])) {
                 $post['two_factor'] = '';
             }
             $gauth = $this->twoFactorPreamble($userID);
             $checked = $gauth->validateCode($post['two_factor'], \time());
             if (!$checked) {
                 $fails = $airBrake->getFailedLoginAttempts($post['username'], $_SERVER['REMOTE_ADDR']) + 1;
                 // Instead of the password, seal a timestamped and
                 // signed message saying the password was correct.
                 // We use a signature with a key local to this Airship
                 // so attackers can't just spam a string constant to
                 // make the person decrypting these strings freak out
                 // and assume the password was compromised.
                 //
                 // False positives are bad. This gives the sysadmin a
                 // surefire way to reliably verify that a log entry is
                 // due to two-factor authentication failing.
                 $message = '**Note: The password was correct; ' . ' invalid 2FA token was provided.** ' . (new \DateTime('now'))->format(\AIRSHIP_DATE_FORMAT);
                 $signed = Base64UrlSafe::encode(Asymmetric::sign($message, $state->keyring['notary.online_signing_key'], true));
                 $airBrake->registerLoginFailure($post['username'], $_SERVER['REMOTE_ADDR'], $fails, new HiddenString($signed . $message));
                 $this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
             }
         }
         if ($user['session_canary']) {
             $_SESSION['session_canary'] = $user['session_canary'];
         } elseif ($this->config('password-reset.logout')) {
             $_SESSION['session_canary'] = $this->acct->createSessionCanary($userID);
         }
         // Regenerate session ID:
         Session::regenerate(true);
         $_SESSION['userid'] = (int) $userID;
         if (!empty($post['remember'])) {
             $autoPilot = Gears::getName('AutoPilot');
             if (IDE_HACKS) {
                 $autoPilot = new AutoPilot();
             }
             $httpsOnly = (bool) $autoPilot::isHTTPSConnection();
             Cookie::setcookie('airship_token', Symmetric::encrypt($this->airship_auth->createAuthToken($userID), $state->keyring['cookie.encrypt_key']), \time() + ($state->universal['long-term-auth-expire'] ?? self::DEFAULT_LONGTERMAUTH_EXPIRE), '/', $state->universal['session_config']['cookie_domain'] ?? '', $httpsOnly ?? false, true);
         }
         \Airship\redirect($this->airship_cabin_prefix);
     } else {
         $fails = $airBrake->getFailedLoginAttempts($post['username'], $_SERVER['REMOTE_ADDR']) + 1;
         // If the server is setup (with an EncryptionPublicKey) and the
         // number of failures is above the log threshold, this will
         // encrypt the password guess with the public key so that only
         // the person in possession of the secret key can decrypt it.
         $airBrake->registerLoginFailure($post['username'], $_SERVER['REMOTE_ADDR'], $fails, new HiddenString($post['passphrase']));
         $this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
     }
 }
Пример #3
0
<?php

declare (strict_types=1);
use Airship\Engine\{AutoPilot, Gears, State};
/**
 * This loads the Cabin configuration, and selects the active cabin.
 *
 * @global State $state
 */
$ap = Gears::getName('AutoPilot');
// Needed for IDE code completion:
if (IDE_HACKS) {
    $ap = new AutoPilot();
}
/**
 * Cache the cabin configuration
 */
$cabinDisabled = false;
if (\file_exists(ROOT . '/tmp/cache/cabin_data.json')) {
    // Load the cabins from cache
    $config = \Airship\loadJSON(ROOT . '/tmp/cache/cabin_data.json');
    foreach ($config['cabins'] as $key => $cabin) {
        if ($ap::isActiveCabinKey($key, !empty($cabin['https']))) {
            $state->active_cabin = $key;
            if ($cabin['enabled']) {
                $cabinDisabled = true;
            }
            break;
        }
    }
    $state->cabins = $config['cabins'];
Пример #4
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'];
                }
Пример #5
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;
 }
Пример #6
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;
 }