diff --git a/src/applications/auth/constants/PhabricatorCookies.php b/src/applications/auth/constants/PhabricatorCookies.php index 31bdf3704c..f85f1fc997 100644 --- a/src/applications/auth/constants/PhabricatorCookies.php +++ b/src/applications/auth/constants/PhabricatorCookies.php @@ -1,179 +1,179 @@ getCookie(self::COOKIE_CLIENTID); if ($value === null || !strlen($value)) { $request->setTemporaryCookie( self::COOKIE_CLIENTID, Filesystem::readRandomCharacters(16)); } } /* -( Next URI Cookie )---------------------------------------------------- */ /** * Set the Next URI cookie. We only write the cookie if it wasn't recently * written, to avoid writing over a real URI with a bunch of "humans.txt" * stuff. See T3793 for discussion. * * @param AphrontRequest Request to write to. * @param string URI to write. * @param bool Write this cookie even if we have a fresh * cookie already. * @return void * * @task next */ public static function setNextURICookie( AphrontRequest $request, $next_uri, $force = false) { if (!$force) { $cookie_value = $request->getCookie(self::COOKIE_NEXTURI); list($set_at, $current_uri) = self::parseNextURICookie($cookie_value); // If the cookie was set within the last 2 minutes, don't overwrite it. // Primarily, this prevents browser requests for resources which do not // exist (like "humans.txt" and various icons) from overwriting a normal // URI like "/feed/". if ($set_at > (time() - 120)) { return; } } $new_value = time().','.$next_uri; $request->setTemporaryCookie(self::COOKIE_NEXTURI, $new_value); } /** * Read the URI out of the Next URI cookie. * * @param AphrontRequest Request to examine. * @return string|null Next URI cookie's URI value. * * @task next */ public static function getNextURICookie(AphrontRequest $request) { $cookie_value = $request->getCookie(self::COOKIE_NEXTURI); list($set_at, $next_uri) = self::parseNextURICookie($cookie_value); return $next_uri; } /** * Parse a Next URI cookie into its components. * * @param string Raw cookie value. * @return list List of timestamp and URI. * * @task next */ private static function parseNextURICookie($cookie) { // Old cookies look like: /uri // New cookies look like: timestamp,/uri - if (!strlen($cookie)) { + if (!phutil_nonempty_string($cookie)) { return null; } if (strpos($cookie, ',') !== false) { list($timestamp, $uri) = explode(',', $cookie, 2); return array((int)$timestamp, $uri); } return array(0, $cookie); } } diff --git a/src/applications/auth/controller/PhabricatorAuthSetExternalController.php b/src/applications/auth/controller/PhabricatorAuthSetExternalController.php index 8b0a44b9dc..2a8bbda7df 100644 --- a/src/applications/auth/controller/PhabricatorAuthSetExternalController.php +++ b/src/applications/auth/controller/PhabricatorAuthSetExternalController.php @@ -1,111 +1,111 @@ getViewer(); $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->withIsEnabled(true) ->execute(); $linkable = array(); foreach ($configs as $config) { if (!$config->getShouldAllowLink()) { continue; } // For now, only buttons get to appear here: for example, we can't // reasonably embed an entire LDAP form into this UI. $provider = $config->getProvider(); if (!$provider->isLoginFormAButton()) { continue; } $linkable[] = $config; } if (!$linkable) { return $this->newDialog() ->setTitle(pht('No Linkable External Providers')) ->appendParagraph( pht( 'Currently, there are no configured external auth providers '. 'which you can link your account to.')) ->addCancelButton('/'); } $text = PhabricatorAuthMessage::loadMessageText( $viewer, PhabricatorAuthLinkMessageType::MESSAGEKEY); - if (!strlen($text)) { + if (!phutil_nonempty_string($text)) { $text = pht( 'You can link your %s account to an external account to '. 'allow you to log in more easily in the future. To continue, choose '. 'an account to link below. If you prefer not to link your account, '. 'you can skip this step.', PlatformSymbols::getPlatformServerName()); } $remarkup_view = new PHUIRemarkupView($viewer, $text); $remarkup_view = phutil_tag( 'div', array( 'class' => 'phui-object-box-instructions', ), $remarkup_view); PhabricatorCookies::setClientIDCookie($request); $view = array(); foreach ($configs as $config) { $provider = $config->getProvider(); $form = $provider->buildLinkForm($this); if ($provider->isLoginFormAButton()) { require_celerity_resource('auth-css'); $form = phutil_tag( 'div', array( 'class' => 'phabricator-link-button pl', ), $form); } $view[] = $form; } $form = id(new AphrontFormView()) ->setViewer($viewer) ->appendControl( id(new AphrontFormSubmitControl()) ->addCancelButton('/', pht('Skip This Step'))); $header = id(new PHUIHeaderView()) ->setHeader(pht('Link External Account')); $box = id(new PHUIObjectBoxView()) ->setViewer($viewer) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($remarkup_view) ->appendChild($view) ->appendChild($form); $main_view = id(new PHUITwoColumnView()) ->setFooter($box); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb(pht('Link External Account')) ->setBorder(true); return $this->newPage() ->setTitle(pht('Link External Account')) ->setCrumbs($crumbs) ->appendChild($main_view); } } diff --git a/src/applications/notification/client/PhabricatorNotificationServerRef.php b/src/applications/notification/client/PhabricatorNotificationServerRef.php index 7788bacaaf..f3a49afc9f 100644 --- a/src/applications/notification/client/PhabricatorNotificationServerRef.php +++ b/src/applications/notification/client/PhabricatorNotificationServerRef.php @@ -1,267 +1,267 @@ type = $type; return $this; } public function getType() { return $this->type; } public function setHost($host) { $this->host = $host; return $this; } public function getHost() { return $this->host; } public function setPort($port) { $this->port = $port; return $this; } public function getPort() { return $this->port; } public function setProtocol($protocol) { $this->protocol = $protocol; return $this; } public function getProtocol() { return $this->protocol; } public function setPath($path) { $this->path = $path; return $this; } public function getPath() { return $this->path; } public function setIsDisabled($is_disabled) { $this->isDisabled = $is_disabled; return $this; } public function getIsDisabled() { return $this->isDisabled; } public static function getLiveServers() { $cache = PhabricatorCaches::getRequestCache(); $refs = $cache->getKey(self::KEY_REFS); if (!$refs) { $refs = self::newRefs(); $cache->setKey(self::KEY_REFS, $refs); } return $refs; } public static function newRefs() { $configs = PhabricatorEnv::getEnvConfig('notification.servers'); $refs = array(); foreach ($configs as $config) { $ref = id(new self()) ->setType($config['type']) ->setHost($config['host']) ->setPort($config['port']) ->setProtocol($config['protocol']) ->setPath(idx($config, 'path')) ->setIsDisabled(idx($config, 'disabled', false)); $refs[] = $ref; } return $refs; } public static function getEnabledServers() { $servers = self::getLiveServers(); foreach ($servers as $key => $server) { if ($server->getIsDisabled()) { unset($servers[$key]); } } return array_values($servers); } public static function getEnabledAdminServers() { $servers = self::getEnabledServers(); foreach ($servers as $key => $server) { if (!$server->isAdminServer()) { unset($servers[$key]); } } return array_values($servers); } public static function getEnabledClientServers($with_protocol) { $servers = self::getEnabledServers(); foreach ($servers as $key => $server) { if ($server->isAdminServer()) { unset($servers[$key]); continue; } $protocol = $server->getProtocol(); if ($protocol != $with_protocol) { unset($servers[$key]); continue; } } return array_values($servers); } public function isAdminServer() { return ($this->type == 'admin'); } public function getURI($to_path = null) { if ($to_path === null || !strlen($to_path)) { $to_path = ''; } else { - $to_path = '/'.ltrim($to_path, '/'); + $to_path = ltrim($to_path, '/'); } $base_path = $this->getPath(); if ($base_path === null || !strlen($base_path)) { $base_path = ''; } else { $base_path = rtrim($base_path, '/'); } - $full_path = $base_path.$to_path; + $full_path = $base_path.'/'.$to_path; $uri = id(new PhutilURI('http://'.$this->getHost())) ->setProtocol($this->getProtocol()) ->setPort($this->getPort()) ->setPath($full_path); $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); if ($instance !== null && strlen($instance)) { $uri->replaceQueryParam('instance', $instance); } return $uri; } public function getWebsocketURI($to_path = null) { $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); if ($instance !== null && strlen($instance)) { $to_path = $to_path.'~'.$instance.'/'; } $uri = $this->getURI($to_path); if ($this->getProtocol() == 'https') { $uri->setProtocol('wss'); } else { $uri->setProtocol('ws'); } return $uri; } public function testClient() { if ($this->isAdminServer()) { throw new Exception( pht('Unable to test client on an admin server!')); } $server_uri = $this->getURI(); try { id(new HTTPSFuture($server_uri)) ->setTimeout(2) ->resolvex(); } catch (HTTPFutureHTTPResponseStatus $ex) { // This is what we expect when things are working correctly. if ($ex->getStatusCode() == 501) { return true; } throw $ex; } throw new Exception( pht('Got HTTP 200, but expected HTTP 501 (WebSocket Upgrade)!')); } public function loadServerStatus() { if (!$this->isAdminServer()) { throw new Exception( pht( 'Unable to load server status: this is not an admin server!')); } $server_uri = $this->getURI('/status/'); list($body) = $this->newFuture($server_uri) ->resolvex(); return phutil_json_decode($body); } public function postMessage(array $data) { if (!$this->isAdminServer()) { throw new Exception( pht('Unable to post message: this is not an admin server!')); } $server_uri = $this->getURI('/'); $payload = phutil_json_encode($data); $this->newFuture($server_uri, $payload) ->setMethod('POST') ->resolvex(); } private function newFuture($uri, $data = null) { if ($data === null) { $future = new HTTPSFuture($uri); } else { $future = new HTTPSFuture($uri, $data); } $future->setTimeout(2); // At one point, a HackerOne researcher reported a "Location:" redirect // attack here (if the attacker can gain control of the notification // server or the configuration). // Although this attack is not particularly concerning, we don't expect // Aphlict to ever issue a "Location:" header, so receiving one indicates // something is wrong and declining to follow the header may make debugging // easier. $future->setFollowLocation(false); return $future; } }