protected function renderLoginForm(AphrontRequest $request, $mode) { $viewer = $request->getUser(); $dialog = id(new AphrontDialogView())->setSubmitURI($this->getLoginURI())->setUser($viewer); if ($mode == 'link') { $dialog->setTitle(pht('Link LDAP Account')); $dialog->addSubmitButton(pht('Link Accounts')); $dialog->addCancelButton($this->getSettingsURI()); } else { if ($mode == 'refresh') { $dialog->setTitle(pht('Refresh LDAP Account')); $dialog->addSubmitButton(pht('Refresh Account')); $dialog->addCancelButton($this->getSettingsURI()); } else { if ($this->shouldAllowRegistration()) { $dialog->setTitle(pht('Login or Register with LDAP')); $dialog->addSubmitButton(pht('Login or Register')); } else { $dialog->setTitle(pht('Login with LDAP')); $dialog->addSubmitButton(pht('Login')); } if ($mode == 'login') { $dialog->addCancelButton($this->getStartURI()); } } } $v_user = $request->getStr('ldap_username'); $e_user = null; $e_pass = null; $errors = array(); if ($request->isHTTPPost()) { // NOTE: This is intentionally vague so as not to disclose whether a // given username exists. $e_user = pht('Invalid'); $e_pass = pht('Invalid'); $errors[] = pht('Username or password are incorrect.'); } $form = id(new PHUIFormLayoutView())->setUser($viewer)->setFullWidth(true)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('LDAP Username'))->setName('ldap_username')->setValue($v_user)->setError($e_user))->appendChild(id(new AphrontFormPasswordControl())->setLabel(pht('LDAP Password'))->setName('ldap_password')->setError($e_pass)); if ($errors) { $errors = id(new PHUIInfoView())->setErrors($errors); } $dialog->appendChild($errors); $dialog->appendChild($form); return $dialog; }
public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); $this->key = $request->getURIData('key'); $this->token = $request->getURIData('token'); $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $alt_uri = new PhutilURI($alt); $alt_domain = $alt_uri->getDomain(); $req_domain = $request->getHost(); $main_domain = id(new PhutilURI($base_uri))->getDomain(); $cache_response = true; if (empty($alt) || $main_domain == $alt_domain) { // Alternate files domain isn't configured or it's set // to the same as the default domain $response = $this->loadFile($viewer); if ($response) { return $response; } $file = $this->getFile(); // when the file is not CDNable, don't allow cache $cache_response = $file->getCanCDN(); } else { if ($req_domain != $alt_domain) { // Alternate domain is configured but this request isn't using it $response = $this->loadFile($viewer); if ($response) { return $response; } $file = $this->getFile(); // if the user can see the file, generate a token; // redirect to the alt domain with the token; $token_uri = $file->getCDNURIWithToken(); $token_uri = new PhutilURI($token_uri); $token_uri = $this->addURIParameters($token_uri); return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($token_uri); } else { // We are using the alternate domain. We don't have authentication // on this domain, so we bypass policy checks when loading the file. $bypass_policies = PhabricatorUser::getOmnipotentUser(); $response = $this->loadFile($bypass_policies); if ($response) { return $response; } $file = $this->getFile(); $acquire_token_uri = id(new PhutilURI($file->getViewURI()))->setDomain($main_domain); $acquire_token_uri = $this->addURIParameters($acquire_token_uri); if ($this->token) { // validate the token, if it is valid, continue $validated_token = $file->validateOneTimeToken($this->token); if (!$validated_token) { $dialog = $this->newDialog()->setShortTitle(pht('Expired File'))->setTitle(pht('File Link Has Expired'))->appendParagraph(pht('The link you followed to view this file is invalid or ' . 'expired.'))->appendParagraph(pht('Continue to generate a new link to the file. You may be ' . 'required to log in.'))->addCancelButton($acquire_token_uri, pht('Continue')); // Build an explicit response so we can respond with HTTP/403 instead // of HTTP/200. $response = id(new AphrontDialogResponse())->setDialog($dialog)->setHTTPResponseCode(403); return $response; } // return the file data without cache headers $cache_response = false; } else { if (!$file->getCanCDN()) { // file cannot be served via cdn, and no token given // redirect to the main domain to aquire a token // This is marked as an "external" URI because it is fully qualified. return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($acquire_token_uri); } } } } $response = new AphrontFileResponse(); if ($cache_response) { $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); } $begin = null; $end = null; // NOTE: It's important to accept "Range" requests when playing audio. // If we don't, Safari has difficulty figuring out how long sounds are // and glitches when trying to loop them. In particular, Safari sends // an initial request for bytes 0-1 of the audio file, and things go south // if we can't respond with a 206 Partial Content. $range = $request->getHTTPHeader('range'); if ($range) { $matches = null; if (preg_match('/^bytes=(\\d+)-(\\d+)$/', $range, $matches)) { // Note that the "Range" header specifies bytes differently than // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1). $begin = (int) $matches[1]; $end = (int) $matches[2] + 1; $response->setHTTPResponseCode(206); $response->setRange($begin, $end - 1); } } else { if (isset($validated_token)) { // We set this on the response, and the response deletes it after the // transfer completes. This allows transfers to be resumed, in theory. $response->setTemporaryFileToken($validated_token); } } $is_viewable = $file->isViewableInBrowser(); $force_download = $request->getExists('download'); if ($is_viewable && !$force_download) { $response->setMimeType($file->getViewableMimeType()); } else { if (!$request->isHTTPPost() && !$alt_domain) { // NOTE: Require POST to download files from the primary domain. We'd // rather go full-bore and do a real CSRF check, but can't currently // authenticate users on the file domain. This should blunt any // attacks based on iframes, script tags, applet tags, etc., at least. // Send the user to the "info" page if they're using some other method. // This is marked as "external" because it is fully qualified. return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); } $response->setMimeType($file->getMimeType()); $response->setDownload($file->getName()); } $iterator = $file->getFileDataIterator($begin, $end); $response->setContentLength($file->getByteSize()); $response->setContentIterator($iterator); return $response; }
/** * Require high security, or prompt the user to enter high security. * * If the user's session is in high security, this method will return a * token. Otherwise, it will throw an exception which will eventually * be converted into a multi-factor authentication workflow. * * @param PhabricatorUser User whose session needs to be in high security. * @param AphrontReqeust Current request. * @param string URI to return the user to if they cancel. * @param bool True to jump partial sessions directly into high * security instead of just upgrading them to full * sessions. * @return PhabricatorAuthHighSecurityToken Security token. * @task hisec */ public function requireHighSecuritySession(PhabricatorUser $viewer, AphrontRequest $request, $cancel_uri, $jump_into_hisec = false) { if (!$viewer->hasSession()) { throw new Exception(pht('Requiring a high-security session from a user with no session!')); } $session = $viewer->getSession(); // Check if the session is already in high security mode. $token = $this->issueHighSecurityToken($session); if ($token) { return $token; } // Load the multi-factor auth sources attached to this account. $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere('userPHID = %s', $viewer->getPHID()); // If the account has no associated multi-factor auth, just issue a token // without putting the session into high security mode. This is generally // easier for users. A minor but desirable side effect is that when a user // adds an auth factor, existing sessions won't get a free pass into hisec, // since they never actually got marked as hisec. if (!$factors) { return $this->issueHighSecurityToken($session, true); } // Check for a rate limit without awarding points, so the user doesn't // get partway through the workflow only to get blocked. PhabricatorSystemActionEngine::willTakeAction(array($viewer->getPHID()), new PhabricatorAuthTryFactorAction(), 0); $validation_results = array(); if ($request->isHTTPPost()) { $request->validateCSRF(); if ($request->getExists(AphrontRequest::TYPE_HISEC)) { // Limit factor verification rates to prevent brute force attacks. PhabricatorSystemActionEngine::willTakeAction(array($viewer->getPHID()), new PhabricatorAuthTryFactorAction(), 1); $ok = true; foreach ($factors as $factor) { $id = $factor->getID(); $impl = $factor->requireImplementation(); $validation_results[$id] = $impl->processValidateFactorForm($factor, $viewer, $request); if (!$impl->isFactorValid($factor, $validation_results[$id])) { $ok = false; } } if ($ok) { // Give the user a credit back for a successful factor verification. PhabricatorSystemActionEngine::willTakeAction(array($viewer->getPHID()), new PhabricatorAuthTryFactorAction(), -1); if ($session->getIsPartial() && !$jump_into_hisec) { // If we have a partial session and are not jumping directly into // hisec, just issue a token without putting it in high security // mode. return $this->issueHighSecurityToken($session, true); } $until = time() + phutil_units('15 minutes in seconds'); $session->setHighSecurityUntil($until); queryfx($session->establishConnection('w'), 'UPDATE %T SET highSecurityUntil = %d WHERE id = %d', $session->getTableName(), $until, $session->getID()); $log = PhabricatorUserLog::initializeNewLog($viewer, $viewer->getPHID(), PhabricatorUserLog::ACTION_ENTER_HISEC); $log->save(); } else { $log = PhabricatorUserLog::initializeNewLog($viewer, $viewer->getPHID(), PhabricatorUserLog::ACTION_FAIL_HISEC); $log->save(); } } } $token = $this->issueHighSecurityToken($session); if ($token) { return $token; } throw id(new PhabricatorAuthHighSecurityRequiredException())->setCancelURI($cancel_uri)->setFactors($factors)->setFactorValidationResults($validation_results); }
public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); $this->key = $request->getURIData('key'); $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $alt_uri = new PhutilURI($alt); $alt_domain = $alt_uri->getDomain(); $req_domain = $request->getHost(); $main_domain = id(new PhutilURI($base_uri))->getDomain(); if (!strlen($alt) || $main_domain == $alt_domain) { // No alternate domain. $should_redirect = false; $is_alternate_domain = false; } else { if ($req_domain != $alt_domain) { // Alternate domain, but this request is on the main domain. $should_redirect = true; $is_alternate_domain = false; } else { // Alternate domain, and on the alternate domain. $should_redirect = false; $is_alternate_domain = true; } } $response = $this->loadFile(); if ($response) { return $response; } $file = $this->getFile(); if ($should_redirect) { return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($file->getCDNURI()); } $response = new AphrontFileResponse(); $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); $response->setCanCDN($file->getCanCDN()); $begin = null; $end = null; // NOTE: It's important to accept "Range" requests when playing audio. // If we don't, Safari has difficulty figuring out how long sounds are // and glitches when trying to loop them. In particular, Safari sends // an initial request for bytes 0-1 of the audio file, and things go south // if we can't respond with a 206 Partial Content. $range = $request->getHTTPHeader('range'); if ($range) { $matches = null; if (preg_match('/^bytes=(\\d+)-(\\d+)$/', $range, $matches)) { // Note that the "Range" header specifies bytes differently than // we do internally: the range 0-1 has 2 bytes (byte 0 and byte 1). $begin = (int) $matches[1]; $end = (int) $matches[2] + 1; $response->setHTTPResponseCode(206); $response->setRange($begin, $end - 1); } } $is_viewable = $file->isViewableInBrowser(); $force_download = $request->getExists('download'); $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); $is_lfs = $request_type == 'git-lfs'; if ($is_viewable && !$force_download) { $response->setMimeType($file->getViewableMimeType()); } else { if (!$request->isHTTPPost() && !$is_alternate_domain && !$is_lfs) { // NOTE: Require POST to download files from the primary domain. We'd // rather go full-bore and do a real CSRF check, but can't currently // authenticate users on the file domain. This should blunt any // attacks based on iframes, script tags, applet tags, etc., at least. // Send the user to the "info" page if they're using some other method. // This is marked as "external" because it is fully qualified. return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI(PhabricatorEnv::getProductionURI($file->getBestURI())); } $response->setMimeType($file->getMimeType()); $response->setDownload($file->getName()); } $iterator = $file->getFileDataIterator($begin, $end); $response->setContentLength($file->getByteSize()); $response->setContentIterator($iterator); return $response; }