/** * @task markup */ public function getMarkupFieldKey($field) { if ($this->shouldUseMarkupCache($field)) { $id = $this->getID(); } else { $id = PhabricatorHash::digest($this->getMarkupText($field)); } return "phriction:{$field}:{$id}"; }
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; }
/** * 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)); }
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); }
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; }
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()); }
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); }
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); }
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(); } }
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); }
public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digest($this->getMarkupText($field)); $id = $this->getID(); return "ponder:Q{$id}:{$field}:{$hash}"; }
<?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; }