/** * Write file data into S3. * @task impl */ public function writeFile($data, array $params) { $s3 = $this->newS3API(); $name = 'phabricator/' . sha1(Filesystem::readRandomBytes(20)); $s3->putObject($data, $this->getBucketName(), $name, $acl = 'private'); return $name; }
public function processRequest() { $user = $this->getRequest()->getUser(); $old_token = id(new PhabricatorConduitCertificateToken())->loadOneWhere('userPHID = %s', $user->getPHID()); if ($old_token) { $old_token->delete(); } $token = id(new PhabricatorConduitCertificateToken())->setUserPHID($user->getPHID())->setToken(sha1(Filesystem::readRandomBytes(128)))->save(); $panel = new AphrontPanelView(); $panel->setHeader('Certificate Install Token'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild('<p class="aphront-form-instructions">Copy and paste this token into ' . 'the prompt given to you by "arc install-certificate":</p>' . '<p style="padding: 0 0 1em 4em;">' . '<strong>' . phutil_escape_html($token->getToken()) . '</strong>' . '</p>' . '<p class="aphront-form-instructions">arc will then complete the ' . 'install process for you.</p>'); return $this->buildStandardPageResponse($panel, array('title' => 'Certificate Install Token')); }
/** * @task internal */ public static function getKey() { if (self::$key === null) { try { self::$key = Filesystem::readRandomBytes(128); } catch (Exception $ex) { // NOTE: We can't throw here! Otherwise we might get a stack trace // including the string that was passed to PhutilOpaqueEnvelope's // constructor. Just die() instead. die("Unable to read random bytes in PhutilOpaqueEnvelope. (This " . "causes an immediate process exit to avoid leaking the envelope " . "contents in a stack trace.)"); } } return self::$key; }
public function testReadRandomBytes() { $number_of_bytes = 1024; $data = Filesystem::readRandomBytes($number_of_bytes); $this->assertTrue(strlen($data) == $number_of_bytes); $data1 = Filesystem::readRandomBytes(128); $data2 = Filesystem::readRandomBytes(128); $this->assertFalse($data1 == $data2); $caught = null; try { Filesystem::readRandomBytes(0); } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof Exception); }
public function processRequest() { $user = $this->getRequest()->getUser(); // Ideally we'd like to verify this, but it's fine to leave it unguarded // for now and verifying it would need some Ajax junk or for the user to // click a button or similar. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $old_token = id(new PhabricatorConduitCertificateToken())->loadOneWhere('userPHID = %s', $user->getPHID()); if ($old_token) { $old_token->delete(); } $token = id(new PhabricatorConduitCertificateToken())->setUserPHID($user->getPHID())->setToken(sha1(Filesystem::readRandomBytes(128)))->save(); $panel = new AphrontPanelView(); $panel->setHeader('Certificate Install Token'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild('<p class="aphront-form-instructions">Copy and paste this token into ' . 'the prompt given to you by "arc install-certificate":</p>' . '<p style="padding: 0 0 1em 4em;">' . '<strong>' . phutil_escape_html($token->getToken()) . '</strong>' . '</p>' . '<p class="aphront-form-instructions">arc will then complete the ' . 'install process for you.</p>'); return $this->buildStandardPageResponse($panel, array('title' => 'Certificate Install Token')); }
public static function generateNewPHID($type, array $config = array()) { $owner = idx($config, 'owner'); $parent = idx($config, 'parent'); if (!$type) { throw new Exception("Can not generate PHID with no type."); } $entropy = Filesystem::readRandomBytes(20); $uniq = sha1($entropy); $uniq = substr($uniq, 0, 20); $phid = 'PHID-' . $type . '-' . $uniq; $phid_rec = new PhabricatorPHID(); $phid_rec->setPHIDType($type); $phid_rec->setOwnerPHID($owner); $phid_rec->setParentPHID($parent); $phid_rec->setPHID($phid); $phid_rec->save(); return $phid; }
public function processRequest() { $request = $this->getRequest(); $repository = id(new PhabricatorRepository())->load($this->id); if (!$repository) { return new Aphront404Response(); } $views = array('basic' => 'Basics', 'tracking' => 'Tracking'); $vcs = $repository->getVersionControlSystem(); if ($vcs == DifferentialRevisionControlSystem::GIT) { if (!$repository->getDetail('github-token')) { $token = substr(base64_encode(Filesystem::readRandomBytes(8)), 0, 8); $repository->setDetail('github-token', $token); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $repository->save(); unset($unguarded); } $views['github'] = 'GitHub'; } $this->repository = $repository; if (!isset($views[$this->view])) { reset($views); $this->view = key($views); } $nav = new AphrontSideNavView(); foreach ($views as $view => $name) { $nav->addNavItem(phutil_render_tag('a', array('class' => $view == $this->view ? 'aphront-side-nav-selected' : null, 'href' => '/repository/edit/' . $repository->getID() . '/' . $view . '/'), phutil_escape_html($name))); } $this->sideNav = $nav; switch ($this->view) { case 'basic': return $this->processBasicRequest(); case 'tracking': return $this->processTrackingRequest(); case 'github': return $this->processGithubRequest(); default: throw new Exception("Unknown view."); } }
public static function newChunkedFile(PhabricatorFileStorageEngine $engine, $length, array $params) { $file = self::initializeNewFile(); $file->setByteSize($length); // TODO: We might be able to test the first chunk in order to figure // this out more reliably, since MIME detection usually examines headers. // However, enormous files are probably always either actually raw data // or reasonable to treat like raw data. $file->setMimeType('application/octet-stream'); $chunked_hash = idx($params, 'chunkedHash'); if ($chunked_hash) { $file->setContentHash($chunked_hash); } else { // See PhabricatorChunkedFileStorageEngine::getChunkedHash() for some // discussion of this. $seed = Filesystem::readRandomBytes(64); $hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput($seed); $file->setContentHash($hash); } $file->setStorageEngine($engine->getEngineIdentifier()); $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle()); $file->setStorageFormat(self::STORAGE_FORMAT_RAW); $file->setIsPartial(1); $file->readPropertiesFromParameters($params); return $file; }
/** * We generate a unique chronological key for each story type because we want * to be able to page through the stream with a cursor (i.e., select stories * after ID = X) so we can efficiently perform filtering after selecting data, * and multiple stories with the same ID make this cumbersome without putting * a bunch of logic in the client. We could use the primary key, but that * would prevent publishing stories which happened in the past. Since it's * potentially useful to do that (e.g., if you're importing another data * source) build a unique key for each story which has chronological ordering. * * @return string A unique, time-ordered key which identifies the story. */ private function generateChronologicalKey() { // Use the epoch timestamp for the upper 32 bits of the key. Default to // the current time if the story doesn't have an explicit timestamp. $time = nonempty($this->storyTime, time()); // Generate a random number for the lower 32 bits of the key. $rand = head(unpack('L', Filesystem::readRandomBytes(4))); // On 32-bit machines, we have to get creative. if (PHP_INT_SIZE < 8) { // We're on a 32-bit machine. if (function_exists('bcadd')) { // Try to use the 'bc' extension. return bcadd(bcmul($time, bcpow(2, 32)), $rand); } else { // Do the math in MySQL. TODO: If we formalize a bc dependency, get // rid of this. $conn_r = id(new PhabricatorFeedStoryData())->establishConnection('r'); $result = queryfx_one($conn_r, 'SELECT (%d << 32) + %d as N', $time, $rand); return $result['N']; } } else { // This is a 64 bit machine, so we can just do the math. return ($time << 32) + $rand; } }
public function save() { if (!$this->getMailKey()) { $this->mailKey = sha1(Filesystem::readRandomBytes(20)); } return parent::save(); }
private function writeEvents() { if (PhabricatorEnv::isReadOnly()) { return; } $events = $this->events; $random = Filesystem::readRandomBytes(32); $request_key = PhabricatorHash::digestForIndex($random); $host_id = $this->loadHostID(php_uname('n')); $context_id = $this->loadEventContextID($this->eventContext); $viewer_id = $this->loadEventViewerID($this->eventViewer); $label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel')); foreach ($events as $event) { $event->setRequestKey($request_key)->setSampleRate($this->sampleRate)->setEventHostID($host_id)->setEventContextID($context_id)->setEventViewerID($viewer_id)->setEventLabelID($label_map[$event->getEventLabel()])->save(); } }
/** * We generate a unique chronological key for each story type because we want * to be able to page through the stream with a cursor (i.e., select stories * after ID = X) so we can efficiently perform filtering after selecting data, * and multiple stories with the same ID make this cumbersome without putting * a bunch of logic in the client. We could use the primary key, but that * would prevent publishing stories which happened in the past. Since it's * potentially useful to do that (e.g., if you're importing another data * source) build a unique key for each story which has chronological ordering. * * @return string A unique, time-ordered key which identifies the story. */ private function generateChronologicalKey() { // Use the epoch timestamp for the upper 32 bits of the key. Default to // the current time if the story doesn't have an explicit timestamp. $time = nonempty($this->storyTime, time()); // Generate a random number for the lower 32 bits of the key. $rand = head(unpack('L', Filesystem::readRandomBytes(4))); return ($time << 32) + $rand; }
/** * Issue a new session key to this user. Phabricator supports different * types of sessions (like "web" and "conduit") and each session type may * have multiple concurrent sessions (this allows a user to be logged in on * multiple browsers at the same time, for instance). * * Note that this method is transport-agnostic and does not set cookies or * issue other types of tokens, it ONLY generates a new session key. * * You can configure the maximum number of concurrent sessions for various * session types in the Phabricator configuration. * * @param string Session type, like "web". * @return string Newly generated session key. */ public function establishSession($session_type) { $conn_w = $this->establishConnection('w'); if (strpos($session_type, '-') !== false) { throw new Exception("Session type must not contain hyphen ('-')!"); } // We allow multiple sessions of the same type, so when a caller requests // a new session of type "web", we give them the first available session in // "web-1", "web-2", ..., "web-N", up to some configurable limit. If none // of these sessions is available, we overwrite the oldest session and // reissue a new one in its place. $session_limit = 1; switch ($session_type) { case 'web': $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web'); break; case 'conduit': $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit'); break; default: throw new Exception("Unknown session type '{$session_type}'!"); } $session_limit = (int) $session_limit; if ($session_limit <= 0) { throw new Exception("Session limit for '{$session_type}' must be at least 1!"); } // Load all the currently active sessions. $sessions = queryfx_all($conn_w, 'SELECT type, sessionStart FROM %T WHERE userPHID = %s AND type LIKE %>', PhabricatorUser::SESSION_TABLE, $this->getPHID(), $session_type . '-'); // Choose which 'type' we'll actually establish, i.e. what number we're // going to append to the basic session type. To do this, just check all // the numbers sequentially until we find an available session. $establish_type = null; $sessions = ipull($sessions, null, 'type'); for ($ii = 1; $ii <= $session_limit; $ii++) { if (empty($sessions[$session_type . '-' . $ii])) { $establish_type = $session_type . '-' . $ii; break; } } // If we didn't find an available session, choose the oldest session and // overwrite it. if (!$establish_type) { $sessions = isort($sessions, 'sessionStart'); $oldest = reset($sessions); $establish_type = $oldest['type']; } // Consume entropy to generate a new session key, forestalling the eventual // heat death of the universe. $entropy = Filesystem::readRandomBytes(20); $session_key = sha1($entropy); // UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); queryfx($conn_w, 'INSERT INTO %T ' . '(userPHID, type, sessionKey, sessionStart)' . ' VALUES ' . '(%s, %s, %s, UNIX_TIMESTAMP()) ' . 'ON DUPLICATE KEY UPDATE ' . 'sessionKey = VALUES(sessionKey), ' . 'sessionStart = VALUES(sessionStart)', self::SESSION_TABLE, $this->getPHID(), $establish_type, $session_key); $log = PhabricatorUserLog::newLog($this, $this, PhabricatorUserLog::ACTION_LOGIN); $log->setDetails(array('session_type' => $session_type, 'session_issued' => $establish_type)); $log->setSession($session_key); $log->save(); return $session_key; }
public static function newAES256IV() { // AES256 uses a 256 bit key, but the initialization vector length is // only 128 bits. $iv = Filesystem::readRandomBytes(phutil_units('128 bits in bytes')); return new PhutilOpaqueEnvelope($iv); }
public function setPassword(PhutilOpaqueEnvelope $envelope) { if (!$this->getPHID()) { throw new Exception('You can not set a password for an unsaved user because their PHID ' . 'is a salt component in the password hash.'); } if (!strlen($envelope->openEnvelope())) { $this->setPasswordHash(''); } else { $this->setPasswordSalt(md5(Filesystem::readRandomBytes(32))); $hash = $this->hashPassword($envelope); $this->setPasswordHash($hash->openEnvelope()); } return $this; }
represent its data. Pass a normal __soft_directory__ to compress, and a __link__ to symlink to the compressed version once compression is complete. The directory will be cloned into __scratch_dir__ and replaced with hardlinks. This is primarily useful if you want to have checkouts of hundreds of different commits in a version-controlled repository, so you can, e.g., run unit tests or other readonly processes on them. EOUSAGE ); } $soft = Filesystem::resolvePath($argv[1]); $dst = $argv[2]; $root = Filesystem::resolvePath($argv[3]); $hash_map = map_directory($soft); $hardened_id = sha1(Filesystem::readRandomBytes(128)); $hardened_id = hash_path($hardened_id); $hardened = $root . '/pit/' . $hardened_id; execx('mkdir -p %s', $hardened); $obj_root = $root . '/obj/'; foreach ($hash_map as $path => $info) { $hash = $info['hash']; $type = $info['type']; $obj = $obj_root . hash_path($hash); $link = $hardened . '/' . $path; $dir = dirname($link); if (!is_dir($dir)) { $ok = mkdir($dir, 0777, $recursive = true); if (!$ok) { throw new Exception(pht("Failed to make directory for '%s'!", $link)); }
public function save() { if (!$this->mailKey) { $this->mailKey = sha1(Filesystem::readRandomBytes(20)); } $result = parent::save(); if ($this->projectsNeedUpdate) { // If we've changed the project PHIDs for this task, update the link // table. ManiphestTaskProject::updateTaskProjects($this); $this->projectsNeedUpdate = false; } if ($this->subscribersNeedUpdate) { // If we've changed the subscriber PHIDs for this task, update the link // table. ManiphestTaskSubscriber::updateTaskSubscribers($this); $this->subscribersNeedUpdate = false; } return $result; }
protected function executeChecks() { $safe_mode = ini_get('safe_mode'); if ($safe_mode) { $message = pht("You have '%s' enabled in your PHP configuration, but Phabricator " . "will not run in safe mode. Safe mode has been deprecated in PHP 5.3 " . "and removed in PHP 5.4.\n\nDisable safe mode to continue.", 'safe_mode'); $this->newIssue('php.safe_mode')->setIsFatal(true)->setName(pht('Disable PHP %s', 'safe_mode'))->setMessage($message)->addPHPConfig('safe_mode'); return; } // Check for `disable_functions` or `disable_classes`. Although it's // possible to disable a bunch of functions (say, `array_change_key_case()`) // and classes and still have Phabricator work fine, it's unreasonably // difficult for us to be sure we'll even survive setup if these options // are enabled. Phabricator needs access to the most dangerous functions, // so there is no reasonable configuration value here which actually // provides a benefit while guaranteeing Phabricator will run properly. $disable_options = array('disable_functions', 'disable_classes'); foreach ($disable_options as $disable_option) { $disable_value = ini_get($disable_option); if ($disable_value) { // By default Debian installs the pcntl extension but disables all of // its functions using configuration. Whitelist disabling these // functions so that Debian PHP works out of the box (we do not need to // call these functions from the web UI). This is pretty ridiculous but // it's not the users' fault and they haven't done anything crazy to // get here, so don't make them pay for Debian's unusual choices. // See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=605571 $fatal = true; if ($disable_option == 'disable_functions') { $functions = preg_split('/[, ]+/', $disable_value); $functions = array_filter($functions); foreach ($functions as $k => $function) { if (preg_match('/^pcntl_/', $function)) { unset($functions[$k]); } } if (!$functions) { $fatal = false; } } if ($fatal) { $message = pht("You have '%s' enabled in your PHP configuration.\n\n" . "This option is not compatible with Phabricator. Remove " . "'%s' from your configuration to continue.", $disable_option, $disable_option); $this->newIssue('php.' . $disable_option)->setIsFatal(true)->setName(pht('Remove PHP %s', $disable_option))->setMessage($message)->addPHPConfig($disable_option); } } } $overload_option = 'mbstring.func_overload'; $func_overload = ini_get($overload_option); if ($func_overload) { $message = pht("You have '%s' enabled in your PHP configuration.\n\n" . "This option is not compatible with Phabricator. Disable " . "'%s' in your PHP configuration to continue.", $overload_option, $overload_option); $this->newIssue('php' . $overload_option)->setIsFatal(true)->setName(pht('Disable PHP %s', $overload_option))->setMessage($message)->addPHPConfig($overload_option); } $open_basedir = ini_get('open_basedir'); if ($open_basedir) { // 'open_basedir' restricts which files we're allowed to access with // file operations. This might be okay -- we don't need to write to // arbitrary places in the filesystem -- but we need to access certain // resources. This setting is unlikely to be providing any real measure // of security so warn even if things look OK. $failures = array(); try { $open_libphutil = class_exists('Future'); } catch (Exception $ex) { $failures[] = $ex->getMessage(); } try { $open_arcanist = class_exists('ArcanistDiffParser'); } catch (Exception $ex) { $failures[] = $ex->getMessage(); } $open_urandom = false; try { Filesystem::readRandomBytes(1); $open_urandom = true; } catch (FilesystemException $ex) { $failures[] = $ex->getMessage(); } try { $tmp = new TempFile(); file_put_contents($tmp, '.'); $open_tmp = @fopen((string) $tmp, 'r'); if (!$open_tmp) { $failures[] = pht("Unable to read temporary file '%s'.", (string) $tmp); } } catch (Exception $ex) { $message = $ex->getMessage(); $dir = sys_get_temp_dir(); $failures[] = pht("Unable to open temp files from '%s': %s", $dir, $message); } $issue = $this->newIssue('php.open_basedir')->setName(pht('Disable PHP %s', 'open_basedir'))->addPHPConfig('open_basedir'); if ($failures) { $message = pht("Your server is configured with '%s', which prevents Phabricator " . "from opening files it requires access to.\n\n" . "Disable this setting to continue.\n\nFailures:\n\n%s", 'open_basedir', implode("\n\n", $failures)); $issue->setIsFatal(true)->setMessage($message); return; } else { $summary = pht("You have '%s' configured in your PHP settings, which " . "may cause some features to fail.", 'open_basedir'); $message = pht("You have '%s' configured in your PHP settings. Although this " . "setting appears permissive enough that Phabricator will work " . "properly, you may still run into problems because of it.\n\n" . "Consider disabling '%s'.", 'open_basedir', 'open_basedir'); $issue->setSummary($summary)->setMessage($message); } } }
public static function newChunkedFile(PhabricatorFileStorageEngine $engine, $length, array $params) { $file = self::initializeNewFile(); $file->setByteSize($length); // NOTE: Once we receive the first chunk, we'll detect its MIME type and // update the parent file. This matters for large media files like video. $file->setMimeType('application/octet-stream'); $chunked_hash = idx($params, 'chunkedHash'); if ($chunked_hash) { $file->setContentHash($chunked_hash); } else { // See PhabricatorChunkedFileStorageEngine::getChunkedHash() for some // discussion of this. $seed = Filesystem::readRandomBytes(64); $hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput($seed); $file->setContentHash($hash); } $file->setStorageEngine($engine->getEngineIdentifier()); $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle()); // Chunked files are always stored raw because they do not actually store // data. The chunks do, and can be individually formatted. $file->setStorageFormat(PhabricatorFileRawStorageFormat::FORMATKEY); $file->setIsPartial(1); $file->readPropertiesFromParameters($params); return $file; }
public function processRequest(AphrontRequest $request) { $viewer = $request->getUser(); $user = $this->getUser(); $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($viewer, $request, '/settings/'); $vcspassword = id(new PhabricatorRepositoryVCSPassword())->loadOneWhere('userPHID = %s', $user->getPHID()); if (!$vcspassword) { $vcspassword = id(new PhabricatorRepositoryVCSPassword()); $vcspassword->setUserPHID($user->getPHID()); } $panel_uri = $this->getPanelURI('?saved=true'); $errors = array(); $e_password = true; $e_confirm = true; if ($request->isFormPost()) { if ($request->getBool('remove')) { if ($vcspassword->getID()) { $vcspassword->delete(); return id(new AphrontRedirectResponse())->setURI($panel_uri); } } $new_password = $request->getStr('password'); $confirm = $request->getStr('confirm'); if (!strlen($new_password)) { $e_password = pht('Required'); $errors[] = pht('Password is required.'); } else { $e_password = null; } if (!strlen($confirm)) { $e_confirm = pht('Required'); $errors[] = pht('You must confirm the new password.'); } else { $e_confirm = null; } if (!$errors) { $envelope = new PhutilOpaqueEnvelope($new_password); try { // NOTE: This test is against $viewer (not $user), so that the error // message below makes sense in the case that the two are different, // and because an admin reusing their own password is bad, while // system agents generally do not have passwords anyway. $same_password = $viewer->comparePassword($envelope); } catch (PhabricatorPasswordHasherUnavailableException $ex) { // If we're missing the hasher, just let the user continue. $same_password = false; } if ($new_password !== $confirm) { $e_password = pht('Does Not Match'); $e_confirm = pht('Does Not Match'); $errors[] = pht('Password and confirmation do not match.'); } else { if ($same_password) { $e_password = pht('Not Unique'); $e_confirm = pht('Not Unique'); $errors[] = pht('This password is the same as another password associated ' . 'with your account. You must use a unique password for ' . 'VCS access.'); } else { if (PhabricatorCommonPasswords::isCommonPassword($new_password)) { $e_password = pht('Very Weak'); $e_confirm = pht('Very Weak'); $errors[] = pht('This password is extremely weak: it is one of the most common ' . 'passwords in use. Choose a stronger password.'); } } } if (!$errors) { $vcspassword->setPassword($envelope, $user); $vcspassword->save(); return id(new AphrontRedirectResponse())->setURI($panel_uri); } } } $title = pht('Set VCS Password'); $form = id(new AphrontFormView())->setUser($viewer)->appendRemarkupInstructions(pht('To access repositories hosted by Phabricator over HTTP, you must ' . 'set a version control password. This password should be unique.' . "\n\n" . "This password applies to all repositories available over " . "HTTP.")); if ($vcspassword->getID()) { $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('Current Password'))->setDisabled(true)->setValue('********************')); } else { $form->appendChild(id(new AphrontFormMarkupControl())->setLabel(pht('Current Password'))->setValue(phutil_tag('em', array(), pht('No Password Set')))); } $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setName('password')->setLabel(pht('New VCS Password'))->setError($e_password))->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setName('confirm')->setLabel(pht('Confirm VCS Password'))->setError($e_confirm))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Change Password'))); if (!$vcspassword->getID()) { $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $suggest = Filesystem::readRandomBytes(128); $suggest = preg_replace('([^A-Za-z0-9/!().,;{}^&*%~])', '', $suggest); $suggest = substr($suggest, 0, 20); if ($is_serious) { $form->appendRemarkupInstructions(pht('Having trouble coming up with a good password? Try this randomly ' . 'generated one, made by a computer:' . "\n\n" . "`%s`", $suggest)); } else { $form->appendRemarkupInstructions(pht('Having trouble coming up with a good password? Try this ' . 'artisinal password, hand made in small batches by our expert ' . 'craftspeople: ' . "\n\n" . "`%s`", $suggest)); } } $hash_envelope = new PhutilOpaqueEnvelope($vcspassword->getPasswordHash()); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Current Algorithm'))->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName($hash_envelope))); $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Best Available Algorithm'))->setValue(PhabricatorPasswordHasher::getBestAlgorithmName())); if (strlen($hash_envelope->openEnvelope())) { try { $can_upgrade = PhabricatorPasswordHasher::canUpgradeHash($hash_envelope); } catch (PhabricatorPasswordHasherUnavailableException $ex) { $can_upgrade = false; $errors[] = pht('Your VCS 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.'); $errors[] = pht('You can set a new password to replace the old password.'); } if ($can_upgrade) { $errors[] = pht('The strength of your stored VCS password hash can be upgraded. ' . 'To upgrade, either: use the password to authenticate with a ' . 'repository; or change your password.'); } } $object_box = id(new PHUIObjectBoxView())->setHeaderText($title)->setForm($form)->setFormErrors($errors); $remove_form = id(new AphrontFormView())->setUser($viewer); if ($vcspassword->getID()) { $remove_form->addHiddenInput('remove', true)->appendRemarkupInstructions(pht('You can remove your VCS password, which will prevent your ' . 'account from accessing repositories.'))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Remove Password'))); } else { $remove_form->appendRemarkupInstructions(pht('You do not currently have a VCS password set. If you set one, you ' . 'can remove it here later.')); } $remove_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Remove VCS Password'))->setForm($remove_form); $saved = null; if ($request->getBool('saved')) { $saved = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_NOTICE)->setTitle(pht('Password Updated'))->appendChild(pht('Your VCS password has been updated.')); } return array($saved, $object_box, $remove_box); }
public static function newChunkHandle() { $seed = Filesystem::readRandomBytes(64); return PhabricatorHash::digestForIndex($seed); }
public static function runSetup() { header("Content-Type: text/plain"); self::write("PHABRICATOR SETUP\n\n"); // Force browser to stop buffering. self::write(str_repeat(' ', 2048)); usleep(250000); self::write("This setup mode will guide you through setting up your " . "Phabricator configuration.\n"); self::writeHeader("CORE CONFIGURATION"); // NOTE: Test this first since other tests depend on the ability to // execute system commands and will fail if safe_mode is enabled. $safe_mode = ini_get('safe_mode'); if ($safe_mode) { self::writeFailure(); self::write("Setup failure! You have 'safe_mode' enabled. Phabricator will not " . "run in safe mode, and it has been deprecated in PHP 5.3 and removed " . "in PHP 5.4.\n"); return; } else { self::write(" okay PHP's deprecated 'safe_mode' is disabled.\n"); } // NOTE: Also test this early since we can't include files from other // libraries if this is set strictly. $open_basedir = ini_get('open_basedir'); if ($open_basedir) { // 'open_basedir' restricts which files we're allowed to access with // file operations. This might be okay -- we don't need to write to // arbitrary places in the filesystem -- but we need to access certain // resources. This setting is unlikely to be providing any real measure // of security so warn even if things look OK. try { $open_libphutil = class_exists('Future'); } catch (Exception $ex) { $message = $ex->getMessage(); self::write("Unable to load modules from libphutil: {$message}\n"); $open_libphutil = false; } try { $open_arcanist = class_exists('ArcanistDiffParser'); } catch (Exception $ex) { $message = $ex->getMessage(); self::write("Unable to load modules from Arcanist: {$message}\n"); $open_arcanist = false; } $open_urandom = false; try { Filesystem::readRandomBytes(1); $open_urandom = true; } catch (FilesystemException $ex) { self::write($ex->getMessage() . "\n"); } try { $tmp = new TempFile(); file_put_contents($tmp, '.'); $open_tmp = @fopen((string) $tmp, 'r'); } catch (Exception $ex) { $message = $ex->getMessage(); $dir = sys_get_temp_dir(); self::write("Unable to open temp files from '{$dir}': {$message}\n"); $open_tmp = false; } if (!$open_urandom || !$open_tmp || !$open_libphutil || !$open_arcanist) { self::writeFailure(); self::write("Setup failure! Your server is configured with 'open_basedir' in " . "php.ini which prevents Phabricator from opening files it needs to " . "access. Either make the setting more permissive or remove it. It " . "is unlikely you derive significant security benefits from having " . "this configured; files outside this directory can still be " . "accessed through system command execution."); return; } else { self::write("[WARN] You have an 'open_basedir' configured in your php.ini. " . "Although the setting seems permissive enough that Phabricator " . "will run properly, you may run into problems because of it. It is " . "unlikely you gain much real security benefit from having it " . "configured, because the application can still access files outside " . "the 'open_basedir' by running system commands.\n"); } } else { self::write(" okay 'open_basedir' is not set.\n"); } if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) { self::write("[WARN] You have not configured 'security.alternate-file-domain'. " . "This makes your installation vulnerable to attack. Make sure you " . "read the documentation for this parameter and understand the " . "consequences of leaving it unconfigured.\n"); } $path = getenv('PATH'); if (empty($path)) { self::writeFailure(); self::write("Setup failure! The environmental \$PATH variable is empty. " . "Phabricator needs to execute system commands like 'svn', 'git', " . "'hg', and 'diff'. Set up your webserver so that it passes a valid " . "\$PATH to the PHP process.\n\n"); if (php_sapi_name() == 'fpm-fcgi') { self::write("You're running php-fpm, so the easiest way to do this is to add " . "this line to your php-fpm.conf:\n\n" . " env[PATH] = /usr/local/bin:/usr/bin:/bin\n\n" . "Then restart php-fpm.\n"); } return; } else { self::write(" okay \$PATH is nonempty.\n"); } self::write("[OKAY] Core configuration OKAY.\n"); self::writeHeader("REQUIRED PHP EXTENSIONS"); $extensions = array('mysql', 'hash', 'json', 'openssl', 'mbstring', 'iconv', 'curl'); foreach ($extensions as $extension) { $ok = self::requireExtension($extension); if (!$ok) { self::writeFailure(); self::write("Setup failure! Install PHP extension '{$extension}'."); return; } } list($err, $stdout, $stderr) = exec_manual('which php'); if ($err) { self::writeFailure(); self::write("Unable to locate 'php' on the command line from the web " . "server. Verify that 'php' is in the webserver's PATH.\n" . " err: {$err}\n" . "stdout: {$stdout}\n" . "stderr: {$stderr}\n"); return; } else { self::write(" okay PHP binary found on the command line.\n"); $php_bin = trim($stdout); } // NOTE: In cPanel + suphp installs, 'php' may be the PHP CGI SAPI, not the // PHP CLI SAPI. proc_open() will pass the environment to the child process, // which will re-execute the webpage (causing an infinite number of // processes to spawn). To test that the 'php' binary is safe to execute, // we call php_sapi_name() using "env -i" to wipe the environment so it // doesn't execute another reuqest if it's the wrong binary. We can't use // "-r" because php-cgi doesn't support that flag. $tmp_file = new TempFile('sapi.php'); Filesystem::writeFile($tmp_file, '<?php echo php_sapi_name();'); list($err, $stdout, $stderr) = exec_manual('/usr/bin/env -i %s -f %s', $php_bin, $tmp_file); if ($err) { self::writeFailure(); self::write("Unable to execute 'php' on the command line from the web " . "server.\n" . " err: {$err}\n" . "stdout: {$stdout}\n" . "stderr: {$stderr}\n"); return; } else { self::write(" okay PHP is available from the command line.\n"); $sapi = trim($stdout); if ($sapi != 'cli') { self::writeFailure(); self::write("The 'php' binary on this system uses the '{$sapi}' SAPI, but the " . "'cli' SAPI is expected. Replace 'php' with the php-cli SAPI " . "binary, or edit your webserver configuration so the first 'php' " . "in PATH is the 'cli' SAPI.\n\n" . "If you're running cPanel with suphp, the easiest way to fix this " . "is to add '/usr/local/bin' before '/usr/bin' for 'env_path' in " . "suconf.php:\n\n" . ' env_path="/bin:/usr/local/bin:/usr/bin"' . "\n\n"); return; } else { self::write(" okay 'php' is CLI SAPI.\n"); } } $root = dirname(phutil_get_library_root('phabricator')); // On RHEL6, doing a distro install of pcntl makes it available from the // CLI binary but not from the Apache module. This isn't entirely // unreasonable and we don't need it from Apache, so do an explicit test // for CLI availability. list($err, $stdout, $stderr) = exec_manual('php %s', "{$root}/scripts/setup/pcntl_available.php"); if ($err) { self::writeFailure(); self::write("Unable to execute scripts/setup/pcntl_available.php to " . "test for the availability of pcntl from the CLI.\n" . " err: {$err}\n" . "stdout: {$stdout}\n" . "stderr: {$stderr}\n"); return; } else { if (trim($stdout) == 'YES') { self::write(" okay pcntl is available from the command line.\n"); self::write("[OKAY] All extensions OKAY\n"); } else { self::write(" warn pcntl is not available!\n"); self::write("[WARN] *** WARNING *** pcntl extension not available. " . "You will not be able to run daemons.\n"); } } self::writeHeader("GIT SUBMODULES"); if (!Filesystem::pathExists($root . '/.git')) { self::write(" skip Not a git clone.\n\n"); } else { list($info) = execx('(cd %s && git submodule status)', $root); foreach (explode("\n", rtrim($info)) as $line) { $matches = null; if (!preg_match('/^(.)([0-9a-f]{40}) (\\S+)(?: |$)/', $line, $matches)) { self::writeFailure(); self::write("Setup failure! 'git submodule' produced unexpected output:\n" . $line); return; } $status = $matches[1]; $module = $matches[3]; switch ($status) { case '-': case '+': case 'U': self::writeFailure(); self::write("Setup failure! Git submodule '{$module}' is not up to date. " . "Run:\n\n" . " cd {$root} && git submodule update --init\n\n" . "...to update submodules."); return; case ' ': self::write(" okay Git submodule '{$module}' up to date.\n"); break; default: self::writeFailure(); self::write("Setup failure! 'git submodule' reported unknown status " . "'{$status}' for submodule '{$module}'. This is a bug; report " . "it to the Phabricator maintainers."); return; } } } self::write("[OKAY] All submodules OKAY.\n"); self::writeHeader("BASIC CONFIGURATION"); $env = PhabricatorEnv::getEnvConfig('phabricator.env'); if ($env == 'production' || $env == 'default' || $env == 'development') { self::writeFailure(); self::write("Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is " . "a Phabricator environmental default. You should create a custom " . "environmental configuration instead of editing the defaults " . "directly. See this document for instructions:\n"); self::writeDoc('article/Configuration_Guide.html'); return; } else { $host = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $host_uri = new PhutilURI($host); $protocol = $host_uri->getProtocol(); $allowed_protocols = array('http' => true, 'https' => true); if (empty($allowed_protocols[$protocol])) { self::writeFailure(); self::write("You must specify the protocol over which your host works (e.g.: " . "\"http:// or https://\")\nin your custom config file.\nRefer to " . "'default.conf.php' for documentation on configuration options.\n"); return; } if (preg_match('/.*\\/$/', $host)) { self::write(" okay phabricator.base-uri protocol\n"); } else { self::writeFailure(); self::write("You must add a trailing slash at the end of the host\n(e.g.: " . "\"http://phabricator.example.com/ instead of " . "http://phabricator.example.com\")\nin your custom config file." . "\nRefer to 'default.conf.php' for documentation on configuration " . "options.\n"); return; } $host_domain = $host_uri->getDomain(); if (strpos($host_domain, '.') !== false) { self::write(" okay phabricator.base-uri domain\n"); } else { self::writeFailure(); self::write("You must host Phabricator on a domain that contains a dot ('.'). " . "The current domain, '{$host_domain}', does not have a dot, so some " . "browsers will not set cookies on it. For instance, " . "'http://example.com/ is OK, but 'http://example/' won't work. " . "If you are using localhost, create an entry in the hosts file like " . "'127.0.0.1 example.com', and access the localhost with " . "'http://example.com/'."); return; } } $timezone = nonempty(PhabricatorEnv::getEnvConfig('phabricator.timezone'), ini_get('date.timezone')); if (!$timezone) { self::writeFailure(); self::write("Setup failure! Your configuration fails to specify a server " . "timezone. Either set 'date.timezone' in your php.ini or " . "'phabricator.timezone' in your Phabricator configuration. See the " . "PHP documentation for a list of supported timezones:\n\n" . "http://www.php.net/manual/en/timezones.php\n"); return; } else { self::write(" okay Timezone '{$timezone}' configured.\n"); } self::write("[OKAY] Basic configuration OKAY\n"); $issue_gd_warning = false; self::writeHeader('GD LIBRARY'); if (extension_loaded('gd')) { self::write(" okay Extension 'gd' is loaded.\n"); $image_type_map = array('imagepng' => 'PNG', 'imagegif' => 'GIF', 'imagejpeg' => 'JPEG'); foreach ($image_type_map as $function => $image_type) { if (function_exists($function)) { self::write(" okay Support for '{$image_type}' is available.\n"); } else { self::write(" warn Support for '{$image_type}' is not available!\n"); $issue_gd_warning = true; } } } else { self::write(" warn Extension 'gd' is not loaded.\n"); $issue_gd_warning = true; } if ($issue_gd_warning) { self::write("[WARN] The 'gd' library is missing or lacks full support. " . "Phabricator will not be able to generate image thumbnails without " . "gd.\n"); } else { self::write("[OKAY] 'gd' loaded and has full image type support.\n"); } self::writeHeader('FACEBOOK INTEGRATION'); $fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); if (!$fb_auth) { self::write(" skip 'facebook.auth-enabled' not enabled.\n"); } else { self::write(" okay 'facebook.auth-enabled' is enabled.\n"); $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); if (!$app_id) { self::writeFailure(); self::write("Setup failure! 'facebook.auth-enabled' is true but there is no " . "setting for 'facebook.application-id'.\n"); return; } else { self::write(" okay 'facebook.application-id' is set.\n"); } if (!is_string($app_id)) { self::writeFailure(); self::write("Setup failure! 'facebook.application-id' should be a string."); return; } else { self::write(" okay 'facebook.application-id' is string.\n"); } if (!$app_secret) { self::writeFailure(); self::write("Setup failure! 'facebook.auth-enabled' is true but there is no " . "setting for 'facebook.application-secret'."); return; } else { self::write(" okay 'facebook.application-secret is set.\n"); } self::write("[OKAY] Facebook integration OKAY\n"); } self::writeHeader("MySQL DATABASE & STORAGE CONFIGURATION"); $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider'); $conn_user = $conf->getUser(); $conn_pass = $conf->getPassword(); $conn_host = $conf->getHost(); $timeout = ini_get('mysql.connect_timeout'); if ($timeout > 5) { self::writeNote("Your MySQL connect timeout is very high ({$timeout} seconds). " . "Consider reducing it to 5 or below by setting " . "'mysql.connect_timeout' in your php.ini."); } self::write(" okay Trying to connect to MySQL database " . "{$conn_user}@{$conn_host}...\n"); ini_set('mysql.connect_timeout', 2); $conn_raw = PhabricatorEnv::newObjectFromConfig('mysql.implementation', array(array('user' => $conn_user, 'pass' => $conn_pass, 'host' => $conn_host, 'database' => null))); try { queryfx($conn_raw, 'SELECT 1'); self::write(" okay Connection successful!\n"); } catch (AphrontQueryConnectionException $ex) { $message = $ex->getMessage(); self::writeFailure(); self::write("Setup failure! MySQL exception: {$message} \n" . "Edit Phabricator configuration keys 'mysql.user', " . "'mysql.host' and 'mysql.pass' to enable Phabricator " . "to connect."); return; } $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); $engines = ipull($engines, 'Support', 'Engine'); $innodb = idx($engines, 'InnoDB'); if ($innodb != 'YES' && $innodb != 'DEFAULT') { self::writeFailure(); self::write("Setup failure! The 'InnoDB' engine is not available. Enable " . "InnoDB in your MySQL configuration. If you already created tables, " . "MySQL incorrectly used some other engine. You need to convert " . "them or drop and reinitialize them."); return; } else { self::write(" okay InnoDB is available.\n"); } $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); $databases = ipull($databases, 'Database', 'Database'); if (empty($databases[$namespace . '_meta_data'])) { self::writeFailure(); self::write("Setup failure! You haven't run 'bin/storage upgrade'. See this " . "article for instructions:\n"); self::writeDoc('article/Configuration_Guide.html'); return; } else { self::write(" okay Databases have been initialized.\n"); } $index_min_length = queryfx_one($conn_raw, 'SHOW VARIABLES LIKE %s', 'ft_min_word_len'); $index_min_length = idx($index_min_length, 'Value', 4); if ($index_min_length >= 4) { self::writeNote("MySQL is configured with a 'ft_min_word_len' of 4 or greater, which " . "means you will not be able to search for 3-letter terms. Consider " . "setting this in your configuration:\n" . "\n" . " [mysqld]\n" . " ft_min_word_len=3\n" . "\n" . "Then optionally run:\n" . "\n" . " REPAIR TABLE {$namespace}_search.search_documentfield QUICK;\n" . "\n" . "...to reindex existing documents."); } $max_allowed_packet = queryfx_one($conn_raw, 'SHOW VARIABLES LIKE %s', 'max_allowed_packet'); $max_allowed_packet = idx($max_allowed_packet, 'Value', PHP_INT_MAX); $recommended_minimum = 1024 * 1024; if ($max_allowed_packet < $recommended_minimum) { self::writeNote("MySQL is configured with a small 'max_allowed_packet' " . "('{$max_allowed_packet}'), which may cause some large writes to " . "fail. Consider raising this to at least {$recommended_minimum}."); } else { self::write(" okay max_allowed_packet = {$max_allowed_packet}.\n"); } $local_key = 'storage.local-disk.path'; $local_path = PhabricatorEnv::getEnvConfig($local_key); if ($local_path) { if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) { self::writeFailure(); self::write("Setup failure! You have configured local disk storage but the " . "path you specified ('{$local_path}') does not exist or is not " . "readable or writable.\n"); if ($open_basedir) { self::write("You have an 'open_basedir' setting -- make sure Phabricator is " . "allowed to open files in the local storage directory.\n"); } return; } else { self::write(" okay Local disk storage exists and is writable.\n"); } } else { self::write(" skip Not configured for local disk storage.\n"); } $selector = PhabricatorEnv::getEnvConfig('storage.engine-selector'); try { $storage_selector_exists = class_exists($selector); } catch (Exception $ex) { $storage_selector_exists = false; } if ($storage_selector_exists) { self::write(" okay Using '{$selector}' as a storage engine selector.\n"); } else { self::writeFailure(); self::write("Setup failure! You have configured '{$selector}' as a storage engine " . "selector but it does not exist or could not be loaded.\n"); return; } self::write("[OKAY] Database and storage configuration OKAY\n"); self::writeHeader("OUTBOUND EMAIL CONFIGURATION"); $have_adapter = false; $is_ses = false; $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter'); switch ($adapter) { case 'PhabricatorMailImplementationPHPMailerLiteAdapter': $have_adapter = true; if (!Filesystem::pathExists('/usr/bin/sendmail') && !Filesystem::pathExists('/usr/sbin/sendmail')) { self::writeFailure(); self::write("Setup failure! You don't have a 'sendmail' binary on this system " . "but outbound email is configured to use sendmail. Install an MTA " . "(like sendmail, qmail or postfix) or use a different outbound " . "mail configuration. See this guide for configuring outbound " . "email:\n"); self::writeDoc('article/Configuring_Outbound_Email.html'); return; } else { self::write(" okay Sendmail is configured.\n"); } break; case 'PhabricatorMailImplementationAmazonSESAdapter': $is_ses = true; $have_adapter = true; if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) { self::writeFailure(); self::write("Setup failure! 'metamta.can-send-as-user' must be false when " . "configured with Amazon SES."); return; } else { self::write(" okay Sender config looks okay.\n"); } if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) { self::writeFailure(); self::write("Setup failure! 'amazon-ses.access-key' is not set, but " . "outbound mail is configured to deliver via Amazon SES."); return; } else { self::write(" okay Amazon SES access key is set.\n"); } if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) { self::writeFailure(); self::write("Setup failure! 'amazon-ses.secret-key' is not set, but " . "outbound mail is configured to deliver via Amazon SES."); return; } else { self::write(" okay Amazon SES secret key is set.\n"); } if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) { self::writeNote("Your configuration uses Amazon SES to deliver email but tries " . "to send it immediately. This will work, but it's slow. " . "Consider configuring the MetaMTA daemon."); } break; case 'PhabricatorMailImplementationTestAdapter': self::write(" skip You have disabled outbound email.\n"); break; default: self::write(" skip Configured with a custom adapter.\n"); break; } if ($have_adapter) { $default = PhabricatorEnv::getEnvConfig('metamta.default-address'); if (!$default || $default == '*****@*****.**') { self::writeFailure(); self::write("Setup failure! You have not set 'metamta.default-address'."); return; } else { self::write(" okay metamta.default-address is set.\n"); } if ($is_ses) { self::writeNote("Make sure you've verified your 'from' address ('{$default}') with " . "Amazon SES. Until you verify it, you will be unable to send mail " . "using Amazon SES."); } $domain = PhabricatorEnv::getEnvConfig('metamta.domain'); if (!$domain || $domain == 'example.com') { self::writeFailure(); self::write("Setup failure! You have not set 'metamta.domain'."); return; } else { self::write(" okay metamta.domain is set.\n"); } self::write("[OKAY] Mail configuration OKAY\n"); } self::writeHeader('CONFIG CLASSES'); foreach (PhabricatorEnv::getRequiredClasses() as $key => $instanceof) { $config = PhabricatorEnv::getEnvConfig($key); if (!$config) { self::writeNote("'{$key}' is not set."); } else { try { $r = new ReflectionClass($config); if (!$r->isSubclassOf($instanceof)) { throw new Exception("Config setting '{$key}' must be an instance of '{$instanceof}'."); } else { if (!$r->isInstantiable()) { throw new Exception("Config setting '{$key}' must be instantiable."); } } } catch (Exception $ex) { self::writeFailure(); self::write("Setup failure! " . $ex->getMessage()); return; } } } self::write("[OKAY] Config classes OKAY\n"); self::writeHeader('SUCCESS!'); self::write("Congratulations! Your setup seems mostly correct, or at least fairly " . "reasonable.\n\n" . "*** NEXT STEP ***\n" . "Edit your configuration file (conf/{$env}.conf.php) and remove the " . "'phabricator.setup' line to finish installation."); }