예제 #1
0
 /**
  * @task markup
  */
 public function getMarkupFieldKey($field)
 {
     if ($this->shouldUseMarkupCache($field)) {
         $id = $this->getID();
     } else {
         $id = PhabricatorHash::digest($this->getMarkupText($field));
     }
     return "phriction:{$field}:{$id}";
 }
예제 #2
0
 public function setSession($session)
 {
     // Store the hash of the session, not the actual session key, so that
     // seeing the logs doesn't compromise all the sessions which appear in
     // them. This just prevents casual leaks, like in a screenshot.
     if (strlen($session)) {
         $this->session = PhabricatorHash::digest($session);
     }
     return $this;
 }
 public static function newHTTPAuthorization(PhabricatorRepository $repository, PhabricatorUser $viewer, $operation)
 {
     $lfs_user = self::HTTP_USERNAME;
     $lfs_pass = Filesystem::readRandomCharacters(32);
     $lfs_hash = PhabricatorHash::digest($lfs_pass);
     $ttl = PhabricatorTime::getNow() + phutil_units('1 day in seconds');
     $token = id(new PhabricatorAuthTemporaryToken())->setTokenResource($repository->getPHID())->setTokenType(self::TOKENTYPE)->setTokenCode($lfs_hash)->setUserPHID($viewer->getPHID())->setTemporaryTokenProperty('lfs.operation', $operation)->setTokenExpires($ttl)->save();
     $authorization_header = base64_encode($lfs_user . ':' . $lfs_pass);
     return 'Basic ' . $authorization_header;
 }
예제 #4
0
 /**
  * Digest a string into a password hash. This is similar to @{method:digest},
  * but requires a salt and iterates the hash to increase cost.
  */
 public static function digestPassword(PhutilOpaqueEnvelope $envelope, $salt)
 {
     $result = $envelope->openEnvelope();
     if (!$result) {
         throw new Exception('Trying to digest empty password!');
     }
     for ($ii = 0; $ii < 1000; $ii++) {
         $result = PhabricatorHash::digest($result, $salt);
     }
     return $result;
 }
 public function processAddFactorForm(AphrontFormView $form, AphrontRequest $request, PhabricatorUser $user)
 {
     $totp_token_type = PhabricatorAuthTOTPKeyTemporaryTokenType::TOKENTYPE;
     $key = $request->getStr('totpkey');
     if (strlen($key)) {
         // If the user is providing a key, make sure it's a key we generated.
         // This raises the barrier to theoretical attacks where an attacker might
         // provide a known key (such attacks are already prevented by CSRF, but
         // this is a second barrier to overcome).
         // (We store and verify the hash of the key, not the key itself, to limit
         // how useful the data in the table is to an attacker.)
         $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer($user)->withTokenResources(array($user->getPHID()))->withTokenTypes(array($totp_token_type))->withExpired(false)->withTokenCodes(array(PhabricatorHash::digest($key)))->executeOne();
         if (!$temporary_token) {
             // If we don't have a matching token, regenerate the key below.
             $key = null;
         }
     }
     if (!strlen($key)) {
         $key = self::generateNewTOTPKey();
         // Mark this key as one we generated, so the user is allowed to submit
         // a response for it.
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         id(new PhabricatorAuthTemporaryToken())->setTokenResource($user->getPHID())->setTokenType($totp_token_type)->setTokenExpires(time() + phutil_units('1 hour in seconds'))->setTokenCode(PhabricatorHash::digest($key))->save();
         unset($unguarded);
     }
     $code = $request->getStr('totpcode');
     $e_code = true;
     if ($request->getExists('totp')) {
         $okay = self::verifyTOTPCode($user, new PhutilOpaqueEnvelope($key), $code);
         if ($okay) {
             $config = $this->newConfigForUser($user)->setFactorName(pht('Mobile App (TOTP)'))->setFactorSecret($key);
             return $config;
         } else {
             if (!strlen($code)) {
                 $e_code = pht('Required');
             } else {
                 $e_code = pht('Invalid');
             }
         }
     }
     $form->addHiddenInput('totp', true);
     $form->addHiddenInput('totpkey', $key);
     $form->appendRemarkupInstructions(pht('First, download an authenticator application on your phone. Two ' . 'applications which work well are **Authy** and **Google ' . 'Authenticator**, but any other TOTP application should also work.'));
     $form->appendInstructions(pht('Launch the application on your phone, and add a new entry for ' . 'this Phabricator install. When prompted, scan the QR code or ' . 'manually enter the key shown below into the application.'));
     $prod_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
     $issuer = $prod_uri->getDomain();
     $uri = urisprintf('otpauth://totp/%s:%s?secret=%s&issuer=%s', $issuer, $user->getUsername(), $key, $issuer);
     $qrcode = $this->renderQRCode($uri);
     $form->appendChild($qrcode);
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Key'))->setValue(phutil_tag('strong', array(), $key)));
     $form->appendInstructions(pht('(If given an option, select that this key is "Time Based", not ' . '"Counter Based".)'));
     $form->appendInstructions(pht('After entering the key, the application should display a numeric ' . 'code. Enter that code below to confirm that you have configured ' . 'the authenticator correctly:'));
     $form->appendChild(id(new PHUIFormNumberControl())->setLabel(pht('TOTP Code'))->setName('totpcode')->setValue($code)->setError($e_code));
 }
 public function processRequest(AphrontRequest $request)
 {
     $viewer = $request->getUser();
     $accounts = id(new PhabricatorExternalAccountQuery())->setViewer($viewer)->withUserPHIDs(array($viewer->getPHID()))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->execute();
     $identity_phids = mpull($accounts, 'getPHID');
     $identity_phids[] = $viewer->getPHID();
     $sessions = id(new PhabricatorAuthSessionQuery())->setViewer($viewer)->withIdentityPHIDs($identity_phids)->execute();
     $handles = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs($identity_phids)->execute();
     $current_key = PhabricatorHash::digest($request->getCookie(PhabricatorCookies::COOKIE_SESSION));
     $rows = array();
     $rowc = array();
     foreach ($sessions as $session) {
         $is_current = phutil_hashes_are_identical($session->getSessionKey(), $current_key);
         if ($is_current) {
             $rowc[] = 'highlighted';
             $button = phutil_tag('a', array('class' => 'small grey button disabled'), pht('Current'));
         } else {
             $rowc[] = null;
             $button = javelin_tag('a', array('href' => '/auth/session/terminate/' . $session->getID() . '/', 'class' => 'small grey button', 'sigil' => 'workflow'), pht('Terminate'));
         }
         $hisec = $session->getHighSecurityUntil() - time();
         $rows[] = array($handles[$session->getUserPHID()]->renderLink(), substr($session->getSessionKey(), 0, 6), $session->getType(), $hisec > 0 ? phutil_format_relative_time($hisec) : null, phabricator_datetime($session->getSessionStart(), $viewer), phabricator_date($session->getSessionExpires(), $viewer), $button);
     }
     $table = new AphrontTableView($rows);
     $table->setNoDataString(pht("You don't have any active sessions."));
     $table->setRowClasses($rowc);
     $table->setHeaders(array(pht('Identity'), pht('Session'), pht('Type'), pht('HiSec'), pht('Created'), pht('Expires'), pht('')));
     $table->setColumnClasses(array('wide', 'n', '', 'right', 'right', 'right', 'action'));
     $terminate_icon = id(new PHUIIconView())->setIconFont('fa-exclamation-triangle');
     $terminate_button = id(new PHUIButtonView())->setText(pht('Terminate All Sessions'))->setHref('/auth/session/terminate/all/')->setTag('a')->setWorkflow(true)->setIcon($terminate_icon);
     $header = id(new PHUIHeaderView())->setHeader(pht('Active Login Sessions'))->addActionLink($terminate_button);
     $hisec = $viewer->getSession()->getHighSecurityUntil() - time();
     if ($hisec > 0) {
         $hisec_icon = id(new PHUIIconView())->setIconFont('fa-lock');
         $hisec_button = id(new PHUIButtonView())->setText(pht('Leave High Security'))->setHref('/auth/session/downgrade/')->setTag('a')->setWorkflow(true)->setIcon($hisec_icon);
         $header->addActionLink($hisec_button);
     }
     $panel = id(new PHUIObjectBoxView())->setHeader($header)->setTable($table);
     return $panel;
 }
 protected function buildWhereClause(AphrontDatabaseConnection $conn_r)
 {
     $where = array();
     if ($this->ids) {
         $where[] = qsprintf($conn_r, 'id IN (%Ld)', $this->ids);
     }
     if ($this->identityPHIDs) {
         $where[] = qsprintf($conn_r, 'userPHID IN (%Ls)', $this->identityPHIDs);
     }
     if ($this->sessionKeys) {
         $hashes = array();
         foreach ($this->sessionKeys as $session_key) {
             $hashes[] = PhabricatorHash::digest($session_key);
         }
         $where[] = qsprintf($conn_r, 'sessionKey IN (%Ls)', $hashes);
     }
     if ($this->sessionTypes) {
         $where[] = qsprintf($conn_r, 'type IN (%Ls)', $this->sessionTypes);
     }
     $where[] = $this->buildPagingClause($conn_r);
     return $this->formatWhereClause($where);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
     $is_all = $id === 'all';
     $query = id(new PhabricatorAuthSessionQuery())->setViewer($viewer)->withIdentityPHIDs(array($viewer->getPHID()));
     if (!$is_all) {
         $query->withIDs(array($id));
     }
     $current_key = PhabricatorHash::digest($request->getCookie(PhabricatorCookies::COOKIE_SESSION));
     $sessions = $query->execute();
     foreach ($sessions as $key => $session) {
         $is_current = phutil_hashes_are_identical($session->getSessionKey(), $current_key);
         if ($is_current) {
             // Don't terminate the current login session.
             unset($sessions[$key]);
         }
     }
     $panel_uri = '/settings/panel/sessions/';
     if (!$sessions) {
         return $this->newDialog()->setTitle(pht('No Matching Sessions'))->appendParagraph(pht('There are no matching sessions to terminate.'))->appendParagraph(pht('(You can not terminate your current login session. To ' . 'terminate it, log out.)'))->addCancelButton($panel_uri);
     }
     if ($request->isDialogFormPost()) {
         foreach ($sessions as $session) {
             $session->delete();
         }
         return id(new AphrontRedirectResponse())->setURI($panel_uri);
     }
     if ($is_all) {
         $title = pht('Terminate Sessions?');
         $short = pht('Terminate Sessions');
         $body = pht('Really terminate all sessions? (Your current login session will ' . 'not be terminated.)');
     } else {
         $title = pht('Terminate Session?');
         $short = pht('Terminate Session');
         $body = pht('Really terminate session %s?', phutil_tag('strong', array(), substr($session->getSessionKey(), 0, 6)));
     }
     return $this->newDialog()->setTitle($title)->setShortTitle($short)->appendParagraph($body)->addSubmitButton(pht('Terminate'))->addCancelButton($panel_uri);
 }
 /**
  * Hash a one-time login key for storage as a temporary token.
  *
  * @param PhabricatorUser User this key is for.
  * @param PhabricatorUserEmail Optionally, email to verify when
  *  link is used.
  * @param string The one time login key.
  * @return string Hash of the key.
  * task onetime
  */
 private function getOneTimeLoginKeyHash(PhabricatorUser $user, PhabricatorUserEmail $email = null, $key = null)
 {
     $parts = array($key, $user->getAccountSecret());
     if ($email) {
         $parts[] = $email->getVerificationCode();
     }
     return PhabricatorHash::digest(implode(':', $parts));
 }
예제 #10
0
 private function generateToken($epoch, $frequency, $key, $len)
 {
     if ($this->getPHID()) {
         $vec = $this->getPHID() . $this->getAccountSecret();
     } else {
         $vec = $this->getAlternateCSRFString();
     }
     if ($this->hasSession()) {
         $vec = $vec . $this->getSession()->getSessionKey();
     }
     $time_block = floor($epoch / $frequency);
     $vec = $vec . $key . $time_block;
     return substr(PhabricatorHash::digest($vec), 0, $len);
 }
예제 #11
0
 public static function newFromFileData($data, array $params = array())
 {
     $selector = PhabricatorEnv::newObjectFromConfig('storage.engine-selector');
     $engines = $selector->selectStorageEngines($data, $params);
     if (!$engines) {
         throw new Exception("No valid storage engines are available!");
     }
     $data_handle = null;
     $engine_identifier = null;
     $exceptions = array();
     foreach ($engines as $engine) {
         $engine_class = get_class($engine);
         try {
             // Perform the actual write.
             $data_handle = $engine->writeFile($data, $params);
             if (!$data_handle || strlen($data_handle) > 255) {
                 // This indicates an improperly implemented storage engine.
                 throw new PhabricatorFileStorageConfigurationException("Storage engine '{$engine_class}' executed writeFile() but did " . "not return a valid handle ('{$data_handle}') to the data: it " . "must be nonempty and no longer than 255 characters.");
             }
             $engine_identifier = $engine->getEngineIdentifier();
             if (!$engine_identifier || strlen($engine_identifier) > 32) {
                 throw new PhabricatorFileStorageConfigurationException("Storage engine '{$engine_class}' returned an improper engine " . "identifier '{$engine_identifier}': it must be nonempty " . "and no longer than 32 characters.");
             }
             // We stored the file somewhere so stop trying to write it to other
             // places.
             break;
         } catch (Exception $ex) {
             if ($ex instanceof PhabricatorFileStorageConfigurationException) {
                 // If an engine is outright misconfigured (or misimplemented), raise
                 // that immediately since it probably needs attention.
                 throw $ex;
             }
             // If an engine doesn't work, keep trying all the other valid engines
             // in case something else works.
             phlog($ex);
             $exceptions[] = $ex;
         }
     }
     if (!$data_handle) {
         throw new PhutilAggregateException("All storage engines failed to write file:", $exceptions);
     }
     $file_name = idx($params, 'name');
     $file_name = self::normalizeFileName($file_name);
     // If for whatever reason, authorPHID isn't passed as a param
     // (always the case with newFromFileDownload()), store a ''
     $authorPHID = idx($params, 'authorPHID');
     $file = new PhabricatorFile();
     $file->setName($file_name);
     $file->setByteSize(strlen($data));
     $file->setAuthorPHID($authorPHID);
     $file->setContentHash(PhabricatorHash::digest($data));
     $file->setStorageEngine($engine_identifier);
     $file->setStorageHandle($data_handle);
     // TODO: This is probably YAGNI, but allows for us to do encryption or
     // compression later if we want.
     $file->setStorageFormat(self::STORAGE_FORMAT_RAW);
     if (isset($params['mime-type'])) {
         $file->setMimeType($params['mime-type']);
     } else {
         $tmp = new TempFile();
         Filesystem::writeFile($tmp, $data);
         $file->setMimeType(Filesystem::getMimeType($tmp));
     }
     $file->save();
     return $file;
 }
예제 #12
0
 public static function calculateEnvironmentHash()
 {
     $keys = self::getKeysForConsistencyCheck();
     $values = array();
     foreach ($keys as $key) {
         $values[$key] = self::getEnvConfigIfExists($key);
     }
     return PhabricatorHash::digest(json_encode($values));
 }
 protected function executeChecks()
 {
     // NOTE: We've already appended `environment.append-paths`, so we don't
     // need to explicitly check for it.
     $path = getenv('PATH');
     if (!$path) {
         $summary = pht('The environmental variable %s is empty. Phabricator will not ' . 'be able to execute some commands.', '$PATH');
         $message = pht("The environmental variable %s is empty. Phabricator needs to execute " . "some system commands, like `%s`, `%s`, `%s`, and `%s`. To execute " . "these commands, the binaries must be available in the webserver's " . "%s. You can set additional paths in Phabricator configuration.", '$PATH', 'svn', 'git', 'hg', 'diff', '$PATH');
         $this->newIssue('config.environment.append-paths')->setName(pht('%s Not Set', '$PATH'))->setSummary($summary)->setMessage($message)->addPhabricatorConfig('environment.append-paths');
         // Bail on checks below.
         return;
     }
     // Users are remarkably industrious at misconfiguring software. Try to
     // catch mistaken configuration of PATH.
     $path_parts = explode(PATH_SEPARATOR, $path);
     $bad_paths = array();
     foreach ($path_parts as $path_part) {
         if (!strlen($path_part)) {
             continue;
         }
         $message = null;
         $not_exists = false;
         foreach (Filesystem::walkToRoot($path_part) as $part) {
             if (!Filesystem::pathExists($part)) {
                 $not_exists = $part;
                 // Walk up so we can tell if this is a readability issue or not.
                 continue;
             } else {
                 if (!is_dir(Filesystem::resolvePath($part))) {
                     $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because '%s' is not a directory.", $path_part, Filesystem::resolvePath($path_part), $part);
                 } else {
                     if (!is_readable($part)) {
                         $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because '%s' is not readable.", $path_part, Filesystem::resolvePath($path_part), $part);
                     } else {
                         if ($not_exists) {
                             $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because '%s' does not exist.", $path_part, Filesystem::resolvePath($path_part), $not_exists);
                         } else {
                             // Everything seems good.
                             break;
                         }
                     }
                 }
             }
             if ($message !== null) {
                 break;
             }
         }
         if ($message === null) {
             if (!phutil_is_windows() && !@file_exists($path_part . '/.')) {
                 $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because it is not traversable (its '%s' " . "permission bit is not set).", $path_part, Filesystem::resolvePath($path_part), '+x');
             }
         }
         if ($message !== null) {
             $bad_paths[$path_part] = $message;
         }
     }
     if ($bad_paths) {
         foreach ($bad_paths as $path_part => $message) {
             $digest = substr(PhabricatorHash::digest($path_part), 0, 8);
             $this->newIssue('config.PATH.' . $digest)->setName(pht('%s Component Unusable', '$PATH'))->setSummary(pht('A component of the configured PATH can not be used by ' . 'the webserver: %s', $path_part))->setMessage(pht("The configured PATH includes a component which is not usable. " . "Phabricator will be unable to find or execute binaries located " . "here:" . "\n\n" . "%s" . "\n\n" . "The user that the webserver runs as must be able to read all " . "the directories in PATH in order to make use of them.", $message))->addPhabricatorConfig('environment.append-paths');
         }
     }
 }
 public function getMarkupFieldKey($field)
 {
     // We can't use ID because synthetic comments don't have it.
     return 'DI:' . PhabricatorHash::digest($this->getContent());
 }
예제 #15
0
 public function getMarkupFieldKey($field)
 {
     $hash = PhabricatorHash::digest($this->getMarkupText($field));
     return $this->getPHID() . ':' . $field . ':' . $hash;
 }
 public function processRequest(AphrontRequest $request)
 {
     $user = $request->getUser();
     $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($user, $request, '/settings/');
     $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
     $min_len = (int) $min_len;
     // NOTE: To change your password, you need to prove you own the account,
     // either by providing the old password or by carrying a token to
     // the workflow from a password reset email.
     $key = $request->getStr('key');
     $token = null;
     if ($key) {
         $token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer($user)->withObjectPHIDs(array($user->getPHID()))->withTokenTypes(array(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE))->withTokenCodes(array(PhabricatorHash::digest($key)))->withExpired(false)->executeOne();
     }
     $e_old = true;
     $e_new = true;
     $e_conf = true;
     $errors = array();
     if ($request->isFormPost()) {
         if (!$token) {
             $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw'));
             if (!$user->comparePassword($envelope)) {
                 $errors[] = pht('The old password you entered is incorrect.');
                 $e_old = pht('Invalid');
             }
         }
         $pass = $request->getStr('new_pw');
         $conf = $request->getStr('conf_pw');
         if (strlen($pass) < $min_len) {
             $errors[] = pht('Your new password is too short.');
             $e_new = pht('Too Short');
         } else {
             if ($pass !== $conf) {
                 $errors[] = pht('New password and confirmation do not match.');
                 $e_conf = pht('Invalid');
             } else {
                 if (PhabricatorCommonPasswords::isCommonPassword($pass)) {
                     $e_new = pht('Very Weak');
                     $e_conf = pht('Very Weak');
                     $errors[] = pht('Your new password is very weak: it is one of the most common ' . 'passwords in use. Choose a stronger password.');
                 }
             }
         }
         if (!$errors) {
             // This write is unguarded because the CSRF token has already
             // been checked in the call to $request->isFormPost() and
             // the CSRF token depends on the password hash, so when it
             // is changed here the CSRF token check will fail.
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             $envelope = new PhutilOpaqueEnvelope($pass);
             id(new PhabricatorUserEditor())->setActor($user)->changePassword($user, $envelope);
             unset($unguarded);
             if ($token) {
                 // Destroy the token.
                 $token->delete();
                 // If this is a password set/reset, kick the user to the home page
                 // after we update their account.
                 $next = '/';
             } else {
                 $next = $this->getPanelURI('?saved=true');
             }
             id(new PhabricatorAuthSessionEngine())->terminateLoginSessions($user, $request->getCookie(PhabricatorCookies::COOKIE_SESSION));
             return id(new AphrontRedirectResponse())->setURI($next);
         }
     }
     $hash_envelope = new PhutilOpaqueEnvelope($user->getPasswordHash());
     if (strlen($hash_envelope->openEnvelope())) {
         try {
             $can_upgrade = PhabricatorPasswordHasher::canUpgradeHash($hash_envelope);
         } catch (PhabricatorPasswordHasherUnavailableException $ex) {
             $can_upgrade = false;
             // Only show this stuff if we aren't on the reset workflow. We can
             // do resets regardless of the old hasher's availability.
             if (!$token) {
                 $errors[] = pht('Your password is currently hashed using an algorithm which is ' . 'no longer available on this install.');
                 $errors[] = pht('Because the algorithm implementation is missing, your password ' . 'can not be used or updated.');
                 $errors[] = pht('To set a new password, request a password reset link from the ' . 'login screen and then follow the instructions.');
             }
         }
         if ($can_upgrade) {
             $errors[] = pht('The strength of your stored password hash can be upgraded. ' . 'To upgrade, either: log out and log in using your password; or ' . 'change your password.');
         }
     }
     $len_caption = null;
     if ($min_len) {
         $len_caption = pht('Minimum password length: %d characters.', $min_len);
     }
     $form = new AphrontFormView();
     $form->setUser($user)->addHiddenInput('key', $key);
     if (!$token) {
         $form->appendChild(id(new AphrontFormPasswordControl())->setLabel(pht('Old Password'))->setError($e_old)->setName('old_pw'));
     }
     $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('New Password'))->setError($e_new)->setName('new_pw'));
     $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('Confirm Password'))->setCaption($len_caption)->setError($e_conf)->setName('conf_pw'));
     $form->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Change Password')));
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Current Algorithm'))->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName(new PhutilOpaqueEnvelope($user->getPasswordHash()))));
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Best Available Algorithm'))->setValue(PhabricatorPasswordHasher::getBestAlgorithmName()));
     $form->appendRemarkupInstructions(pht('NOTE: Changing your password will terminate any other outstanding ' . 'login sessions.'));
     $form_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Change Password'))->setFormSaved($request->getStr('saved'))->setFormErrors($errors)->setForm($form);
     return array($form_box);
 }
예제 #17
0
 public function validateOneTimeToken($token_code)
 {
     $token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withObjectPHIDs(array($this->getPHID()))->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE))->withExpired(false)->withTokenCodes(array(PhabricatorHash::digest($token_code)))->executeOne();
     return $token;
 }
 public final function getResourceURI($resource)
 {
     $root = $this->getSpecification()->getRootDirectory();
     $path = $root . DIRECTORY_SEPARATOR . $resource;
     $data = Filesystem::readFile($path);
     $hash = PhabricatorHash::digest($data);
     $hash = substr($hash, 0, 6);
     $id = $this->getBlog()->getID();
     $uri = '/phame/r/' . $id . '/' . $hash . '/' . $resource;
     $uri = PhabricatorEnv::getCDNURI($uri);
     return $uri;
 }
 private function setAccountKeyAndContinue(PhabricatorExternalAccount $account, $next_uri)
 {
     if ($account->getUserPHID()) {
         throw new Exception(pht('Account is already registered or linked.'));
     }
     // Regenerate the registration secret key, set it on the external account,
     // set a cookie on the user's machine, and redirect them to registration.
     // See PhabricatorAuthRegisterController for discussion of the registration
     // key.
     $registration_key = Filesystem::readRandomCharacters(32);
     $account->setProperty('registrationKey', PhabricatorHash::digest($registration_key));
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $account->save();
     unset($unguarded);
     $this->getRequest()->setTemporaryCookie(PhabricatorCookies::COOKIE_REGISTRATION, $registration_key);
     return id(new AphrontRedirectResponse())->setURI($next_uri);
 }
예제 #20
0
 private function generateToken($epoch, $frequency, $key, $len)
 {
     $time_block = floor($epoch / $frequency);
     $vec = $this->getPHID() . $this->getPasswordHash() . $key . $time_block;
     return substr(PhabricatorHash::digest($vec), 0, $len);
 }
 public function getAuthCSRFCode(AphrontRequest $request)
 {
     $phcid = $request->getCookie(PhabricatorCookies::COOKIE_CLIENTID);
     if (!strlen($phcid)) {
         throw new Exception(pht('Your browser did not submit a "%s" cookie with client state ' . 'information in the request. Check that cookies are enabled. ' . 'If this problem persists, you may need to clear your cookies.', PhabricatorCookies::COOKIE_CLIENTID));
     }
     return PhabricatorHash::digest($phcid);
 }
 private function authenticateGitLFSUser($username, PhutilOpaqueEnvelope $password)
 {
     // Never accept these credentials for requests which aren't LFS requests.
     if (!$this->getIsGitLFSRequest()) {
         return null;
     }
     // If we have the wrong username, don't bother checking if the token
     // is right.
     if ($username !== DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME) {
         return null;
     }
     $lfs_pass = $password->openEnvelope();
     $lfs_hash = PhabricatorHash::digest($lfs_pass);
     $token = id(new PhabricatorAuthTemporaryTokenQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE))->withTokenCodes(array($lfs_hash))->withExpired(false)->executeOne();
     if (!$token) {
         return null;
     }
     $user = id(new PhabricatorPeopleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs(array($token->getUserPHID()))->executeOne();
     if (!$user) {
         return null;
     }
     if (!$user->isUserActivated()) {
         return null;
     }
     $this->gitLFSToken = $token;
     return $user;
 }
 public static function computeMailHash($mail_key, $phid)
 {
     $global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key');
     $hash = PhabricatorHash::digest($mail_key . $global_mail_key . $phid);
     return substr($hash, 0, 16);
 }
 public function willBeginExecution()
 {
     $request = $this->getRequest();
     if ($request->getUser()) {
         // NOTE: Unit tests can set a user explicitly. Normal requests are not
         // permitted to do this.
         PhabricatorTestCase::assertExecutingUnitTests();
         $user = $request->getUser();
     } else {
         $user = new PhabricatorUser();
         $session_engine = new PhabricatorAuthSessionEngine();
         $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
         if (strlen($phsid)) {
             $session_user = $session_engine->loadUserForSession(PhabricatorAuthSession::TYPE_WEB, $phsid);
             if ($session_user) {
                 $user = $session_user;
             }
         } else {
             // If the client doesn't have a session token, generate an anonymous
             // session. This is used to provide CSRF protection to logged-out users.
             $phsid = $session_engine->establishSession(PhabricatorAuthSession::TYPE_WEB, null, $partial = false);
             // This may be a resource request, in which case we just don't set
             // the cookie.
             if ($request->canSetCookies()) {
                 $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid);
             }
         }
         if (!$user->isLoggedIn()) {
             $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid));
         }
         $request->setUser($user);
     }
     PhabricatorEnv::setLocaleCode($user->getTranslation());
     $preferences = $user->loadPreferences();
     if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
         $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE;
         if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
             $console = new DarkConsoleCore();
             $request->getApplicationConfiguration()->setConsole($console);
         }
     }
     // NOTE: We want to set up the user first so we can render a real page
     // here, but fire this before any real logic.
     $restricted = array('code');
     foreach ($restricted as $parameter) {
         if ($request->getExists($parameter)) {
             if (!$this->shouldAllowRestrictedParameter($parameter)) {
                 throw new Exception(pht('Request includes restricted parameter "%s", but this ' . 'controller ("%s") does not whitelist it. Refusing to ' . 'serve this request because it might be part of a redirection ' . 'attack.', $parameter, get_class($this)));
             }
         }
     }
     if ($this->shouldRequireEnabledUser()) {
         if ($user->isLoggedIn() && !$user->getIsApproved()) {
             $controller = new PhabricatorAuthNeedsApprovalController();
             return $this->delegateToController($controller);
         }
         if ($user->getIsDisabled()) {
             $controller = new PhabricatorDisabledUserController();
             return $this->delegateToController($controller);
         }
     }
     $auth_class = 'PhabricatorAuthApplication';
     $auth_application = PhabricatorApplication::getByClass($auth_class);
     // Require partial sessions to finish login before doing anything.
     if (!$this->shouldAllowPartialSessions()) {
         if ($user->hasSession() && $user->getSession()->getIsPartial()) {
             $login_controller = new PhabricatorAuthFinishController();
             $this->setCurrentApplication($auth_application);
             return $this->delegateToController($login_controller);
         }
     }
     // Check if the user needs to configure MFA.
     $need_mfa = $this->shouldRequireMultiFactorEnrollment();
     $have_mfa = $user->getIsEnrolledInMultiFactor();
     if ($need_mfa && !$have_mfa) {
         // Check if the cache is just out of date. Otherwise, roadblock the user
         // and require MFA enrollment.
         $user->updateMultiFactorEnrollment();
         if (!$user->getIsEnrolledInMultiFactor()) {
             $mfa_controller = new PhabricatorAuthNeedsMultiFactorController();
             $this->setCurrentApplication($auth_application);
             return $this->delegateToController($mfa_controller);
         }
     }
     if ($this->shouldRequireLogin()) {
         // This actually means we need either:
         //   - a valid user, or a public controller; and
         //   - permission to see the application; and
         //   - permission to see at least one Space if spaces are configured.
         $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public');
         // If this controller isn't public, and the user isn't logged in, require
         // login.
         if (!$allow_public && !$user->isLoggedIn()) {
             $login_controller = new PhabricatorAuthStartController();
             $this->setCurrentApplication($auth_application);
             return $this->delegateToController($login_controller);
         }
         if ($user->isLoggedIn()) {
             if ($this->shouldRequireEmailVerification()) {
                 if (!$user->getIsEmailVerified()) {
                     $controller = new PhabricatorMustVerifyEmailController();
                     $this->setCurrentApplication($auth_application);
                     return $this->delegateToController($controller);
                 }
             }
         }
         // If Spaces are configured, require that the user have access to at
         // least one. If we don't do this, they'll get confusing error messages
         // later on.
         $spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist();
         if ($spaces) {
             $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($user);
             if (!$viewer_spaces) {
                 $controller = new PhabricatorSpacesNoAccessController();
                 return $this->delegateToController($controller);
             }
         }
         // If the user doesn't have access to the application, don't let them use
         // any of its controllers. We query the application in order to generate
         // a policy exception if the viewer doesn't have permission.
         $application = $this->getCurrentApplication();
         if ($application) {
             id(new PhabricatorApplicationQuery())->setViewer($user)->withPHIDs(array($application->getPHID()))->executeOne();
         }
     }
     if (!$this->shouldAllowLegallyNonCompliantUsers()) {
         $legalpad_class = 'PhabricatorLegalpadApplication';
         $legalpad = id(new PhabricatorApplicationQuery())->setViewer($user)->withClasses(array($legalpad_class))->withInstalled(true)->execute();
         $legalpad = head($legalpad);
         $doc_query = id(new LegalpadDocumentQuery())->setViewer($user)->withSignatureRequired(1)->needViewerSignatures(true);
         if ($user->hasSession() && !$user->getSession()->getIsPartial() && !$user->getSession()->getSignedLegalpadDocuments() && $user->isLoggedIn() && $legalpad) {
             $sign_docs = $doc_query->execute();
             $must_sign_docs = array();
             foreach ($sign_docs as $sign_doc) {
                 if (!$sign_doc->getUserSignature($user->getPHID())) {
                     $must_sign_docs[] = $sign_doc;
                 }
             }
             if ($must_sign_docs) {
                 $controller = new LegalpadDocumentSignController();
                 $this->getRequest()->setURIMap(array('id' => head($must_sign_docs)->getID()));
                 $this->setCurrentApplication($legalpad);
                 return $this->delegateToController($controller);
             } else {
                 $engine = id(new PhabricatorAuthSessionEngine())->signLegalpadDocuments($user, $sign_docs);
             }
         }
     }
     // NOTE: We do this last so that users get a login page instead of a 403
     // if they need to login.
     if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
         return new Aphront403Response();
     }
 }
예제 #25
0
 public function getCelerityHash($data)
 {
     $tail = PhabricatorEnv::getEnvConfig('celerity.resource-hash');
     $hash = PhabricatorHash::digest($data, $tail);
     return substr($hash, 0, 8);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
     $link_type = $request->getURIData('type');
     $key = $request->getURIData('key');
     $email_id = $request->getURIData('emailID');
     if ($request->getUser()->isLoggedIn()) {
         return $this->renderError(pht('You are already logged in.'));
     }
     $target_user = id(new PhabricatorPeopleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withIDs(array($id))->executeOne();
     if (!$target_user) {
         return new Aphront404Response();
     }
     // NOTE: As a convenience to users, these one-time login URIs may also
     // be associated with an email address which will be verified when the
     // URI is used.
     // This improves the new user experience for users receiving "Welcome"
     // emails on installs that require verification: if we did not verify the
     // email, they'd immediately get roadblocked with a "Verify Your Email"
     // error and have to go back to their email account, wait for a
     // "Verification" email, and then click that link to actually get access to
     // their account. This is hugely unwieldy, and if the link was only sent
     // to the user's email in the first place we can safely verify it as a
     // side effect of login.
     // The email hashed into the URI so users can't verify some email they
     // do not own by doing this:
     //
     //  - Add some address you do not own;
     //  - request a password reset;
     //  - change the URI in the email to the address you don't own;
     //  - login via the email link; and
     //  - get a "verified" address you don't control.
     $target_email = null;
     if ($email_id) {
         $target_email = id(new PhabricatorUserEmail())->loadOneWhere('userPHID = %s AND id = %d', $target_user->getPHID(), $email_id);
         if (!$target_email) {
             return new Aphront404Response();
         }
     }
     $engine = new PhabricatorAuthSessionEngine();
     $token = $engine->loadOneTimeLoginKey($target_user, $target_email, $key);
     if (!$token) {
         return $this->newDialog()->setTitle(pht('Unable to Login'))->setShortTitle(pht('Login Failure'))->appendParagraph(pht('The login link you clicked is invalid, out of date, or has ' . 'already been used.'))->appendParagraph(pht('Make sure you are copy-and-pasting the entire link into ' . 'your browser. Login links are only valid for 24 hours, and ' . 'can only be used once.'))->appendParagraph(pht('You can try again, or request a new link via email.'))->addCancelButton('/login/email/', pht('Send Another Email'));
     }
     if ($request->isFormPost()) {
         // If we have an email bound into this URI, verify email so that clicking
         // the link in the "Welcome" email is good enough, without requiring users
         // to go through a second round of email verification.
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         // Nuke the token and all other outstanding password reset tokens.
         // There is no particular security benefit to destroying them all, but
         // it should reduce HackerOne reports of nebulous harm.
         PhabricatorAuthTemporaryToken::revokeTokens($target_user, array($target_user->getPHID()), array(PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE));
         if ($target_email) {
             id(new PhabricatorUserEditor())->setActor($target_user)->verifyEmail($target_user, $target_email);
         }
         unset($unguarded);
         $next = '/';
         if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
             $next = '/settings/panel/external/';
         } else {
             // We're going to let the user reset their password without knowing
             // the old one. Generate a one-time token for that.
             $key = Filesystem::readRandomCharacters(16);
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             id(new PhabricatorAuthTemporaryToken())->setObjectPHID($target_user->getPHID())->setTokenType(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)->setTokenExpires(time() + phutil_units('1 hour in seconds'))->setTokenCode(PhabricatorHash::digest($key))->save();
             unset($unguarded);
             $next = (string) id(new PhutilURI('/settings/panel/password/'))->setQueryParams(array('key' => $key));
             $request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
         }
         PhabricatorCookies::setNextURICookie($request, $next, $force = true);
         return $this->loginUser($target_user);
     }
     // NOTE: We need to CSRF here so attackers can't generate an email link,
     // then log a user in to an account they control via sneaky invisible
     // form submissions.
     switch ($link_type) {
         case PhabricatorAuthSessionEngine::ONETIME_WELCOME:
             $title = pht('Welcome to Phabricator');
             break;
         case PhabricatorAuthSessionEngine::ONETIME_RECOVER:
             $title = pht('Account Recovery');
             break;
         case PhabricatorAuthSessionEngine::ONETIME_USERNAME:
         case PhabricatorAuthSessionEngine::ONETIME_RESET:
         default:
             $title = pht('Login to Phabricator');
             break;
     }
     $body = array();
     $body[] = pht('Use the button below to log in as: %s', phutil_tag('strong', array(), $target_user->getUsername()));
     if ($target_email && !$target_email->getIsVerified()) {
         $body[] = pht('Logging in will verify %s as an email address you own.', phutil_tag('strong', array(), $target_email->getAddress()));
     }
     $body[] = pht('After logging in you should set a password for your account, or ' . 'link your account to an external account that you can use to ' . 'authenticate in the future.');
     $dialog = $this->newDialog()->setTitle($title)->addSubmitButton(pht('Login (%s)', $target_user->getUsername()))->addCancelButton('/');
     foreach ($body as $paragraph) {
         $dialog->appendParagraph($paragraph);
     }
     return id(new AphrontDialogResponse())->setDialog($dialog);
 }
예제 #27
0
 public function getMarkupFieldKey($field)
 {
     $hash = PhabricatorHash::digest($this->getMarkupText($field));
     $id = $this->getID();
     return "ponder:Q{$id}:{$field}:{$hash}";
 }
예제 #28
0
<?php

$table = new PhabricatorUser();
$table->openTransaction();
$conn = $table->establishConnection('w');
$sessions = queryfx_all($conn, 'SELECT userPHID, type, sessionKey FROM %T FOR UPDATE', PhabricatorUser::SESSION_TABLE);
foreach ($sessions as $session) {
    queryfx($conn, 'UPDATE %T SET sessionKey = %s WHERE userPHID = %s AND type = %s', PhabricatorUser::SESSION_TABLE, PhabricatorHash::digest($session['sessionKey']), $session['userPHID'], $session['type']);
}
$table->saveTransaction();
 protected function loadAccountForRegistrationOrLinking($account_key)
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $account = null;
     $provider = null;
     $response = null;
     if (!$account_key) {
         $response = $this->renderError(pht('Request did not include account key.'));
         return array($account, $provider, $response);
     }
     // NOTE: We're using the omnipotent user because the actual user may not
     // be logged in yet, and because we want to tailor an error message to
     // distinguish between "not usable" and "does not exist". We do explicit
     // checks later on to make sure this account is valid for the intended
     // operation. This requires edit permission for completeness and consistency
     // but it won't actually be meaningfully checked because we're using the
     // ominpotent user.
     $account = id(new PhabricatorExternalAccountQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withAccountSecrets(array($account_key))->needImages(true)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
     if (!$account) {
         $response = $this->renderError(pht('No valid linkable account.'));
         return array($account, $provider, $response);
     }
     if ($account->getUserPHID()) {
         if ($account->getUserPHID() != $viewer->getPHID()) {
             $response = $this->renderError(pht('The account you are attempting to register or link is already ' . 'linked to another user.'));
         } else {
             $response = $this->renderError(pht('The account you are attempting to link is already linked ' . 'to your account.'));
         }
         return array($account, $provider, $response);
     }
     $registration_key = $request->getCookie(PhabricatorCookies::COOKIE_REGISTRATION);
     // NOTE: This registration key check is not strictly necessary, because
     // we're only creating new accounts, not linking existing accounts. It
     // might be more hassle than it is worth, especially for email.
     //
     // The attack this prevents is getting to the registration screen, then
     // copy/pasting the URL and getting someone else to click it and complete
     // the process. They end up with an account bound to credentials you
     // control. This doesn't really let you do anything meaningful, though,
     // since you could have simply completed the process yourself.
     if (!$registration_key) {
         $response = $this->renderError(pht('Your browser did not submit a registration key with the request. ' . 'You must use the same browser to begin and complete registration. ' . 'Check that cookies are enabled and try again.'));
         return array($account, $provider, $response);
     }
     // We store the digest of the key rather than the key itself to prevent a
     // theoretical attacker with read-only access to the database from
     // hijacking registration sessions.
     $actual = $account->getProperty('registrationKey');
     $expect = PhabricatorHash::digest($registration_key);
     if ($actual !== $expect) {
         $response = $this->renderError(pht('Your browser submitted a different registration key than the one ' . 'associated with this account. You may need to clear your cookies.'));
         return array($account, $provider, $response);
     }
     $other_account = id(new PhabricatorExternalAccount())->loadAllWhere('accountType = %s AND accountDomain = %s AND accountID = %s
     AND id != %d', $account->getAccountType(), $account->getAccountDomain(), $account->getAccountID(), $account->getID());
     if ($other_account) {
         $response = $this->renderError(pht('The account you are attempting to register with already belongs ' . 'to another user.'));
         return array($account, $provider, $response);
     }
     $provider = PhabricatorAuthProvider::getEnabledProviderByKey($account->getProviderKey());
     if (!$provider) {
         $response = $this->renderError(pht('The account you are attempting to register with uses a nonexistent ' . 'or disabled authentication provider (with key "%s"). An ' . 'administrator may have recently disabled this provider.', $account->getProviderKey()));
         return array($account, $provider, $response);
     }
     return array($account, $provider, null);
 }
 public static function getChunkedHashForInput($input)
 {
     $rehash = PhabricatorHash::digest($input);
     // Add a suffix to identify this as a chunk hash.
     $rehash = substr($rehash, 0, -2) . '-C';
     return $rehash;
 }