Path: blob/master/src/applications/auth/controller/PhabricatorAuthStartController.php
12256 views
<?php12final class PhabricatorAuthStartController3extends PhabricatorAuthController {45public function shouldRequireLogin() {6return false;7}89public function handleRequest(AphrontRequest $request) {10$viewer = $request->getUser();1112if ($viewer->isLoggedIn()) {13// Kick the user home if they are already logged in.14return id(new AphrontRedirectResponse())->setURI('/');15}1617if ($request->isAjax()) {18return $this->processAjaxRequest();19}2021if ($request->isConduit()) {22return $this->processConduitRequest();23}2425// If the user gets this far, they aren't logged in, so if they have a26// user session token we can conclude that it's invalid: if it was valid,27// they'd have been logged in above and never made it here. Try to clear28// it and warn the user they may need to nuke their cookies.2930$session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);31$did_clear = $request->getStr('cleared');3233if ($session_token !== null && strlen($session_token)) {34$kind = PhabricatorAuthSessionEngine::getSessionKindFromToken(35$session_token);36switch ($kind) {37case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:38// If this is an anonymous session. It's expected that they won't39// be logged in, so we can just continue.40break;41default:42// The session cookie is invalid, so try to clear it.43$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);44$request->clearCookie(PhabricatorCookies::COOKIE_SESSION);4546// We've previously tried to clear the cookie but we ended up back47// here, so it didn't work. Hard fatal instead of trying again.48if ($did_clear) {49return $this->renderError(50pht(51'Your login session is invalid, and clearing the session '.52'cookie was unsuccessful. Try clearing your browser cookies.'));53}5455$redirect_uri = $request->getRequestURI();56$redirect_uri->replaceQueryParam('cleared', 1);57return id(new AphrontRedirectResponse())->setURI($redirect_uri);58}59}6061// If we just cleared the session cookie and it worked, clean up after62// ourselves by redirecting to get rid of the "cleared" parameter. The63// the workflow will continue normally.64if ($did_clear) {65$redirect_uri = $request->getRequestURI();66$redirect_uri->removeQueryParam('cleared');67return id(new AphrontRedirectResponse())->setURI($redirect_uri);68}6970$providers = PhabricatorAuthProvider::getAllEnabledProviders();71foreach ($providers as $key => $provider) {72if (!$provider->shouldAllowLogin()) {73unset($providers[$key]);74}75}7677$configs = array();78foreach ($providers as $provider) {79$configs[] = $provider->getProviderConfig();80}8182if (!$providers) {83if ($this->isFirstTimeSetup()) {84// If this is a fresh install, let the user register their admin85// account.86return id(new AphrontRedirectResponse())87->setURI($this->getApplicationURI('/register/'));88}8990return $this->renderError(91pht(92'This server is not configured with any enabled authentication '.93'providers which can be used to log in. If you have accidentally '.94'locked yourself out by disabling all providers, you can use `%s` '.95'to recover access to an account.',96'./bin/auth recover <username>'));97}9899$next_uri = $request->getStr('next');100if (phutil_nonempty_string($next_uri)) {101if ($this->getDelegatingController()) {102// Only set a next URI from the request path if this controller was103// delegated to, which happens when a user tries to view a page which104// requires them to login.105106// If this controller handled the request directly, we're on the main107// login page, and never want to redirect the user back here after they108// login.109$next_uri = (string)$this->getRequest()->getRequestURI();110}111}112113if (!$request->isFormPost()) {114if (phutil_nonempty_string($next_uri)) {115PhabricatorCookies::setNextURICookie($request, $next_uri);116}117PhabricatorCookies::setClientIDCookie($request);118}119120$auto_response = $this->tryAutoLogin($providers);121if ($auto_response) {122return $auto_response;123}124125$invite = $this->loadInvite();126127$not_buttons = array();128$are_buttons = array();129$providers = msort($providers, 'getLoginOrder');130foreach ($providers as $provider) {131if ($invite) {132$form = $provider->buildInviteForm($this);133} else {134$form = $provider->buildLoginForm($this);135}136if ($provider->isLoginFormAButton()) {137$are_buttons[] = $form;138} else {139$not_buttons[] = $form;140}141}142143$out = array();144$out[] = $not_buttons;145if ($are_buttons) {146require_celerity_resource('auth-css');147148foreach ($are_buttons as $key => $button) {149$are_buttons[$key] = phutil_tag(150'div',151array(152'class' => 'phabricator-login-button mmb',153),154$button);155}156157// If we only have one button, add a second pretend button so that we158// always have two columns. This makes it easier to get the alignments159// looking reasonable.160if (count($are_buttons) == 1) {161$are_buttons[] = null;162}163164$button_columns = id(new AphrontMultiColumnView())165->setFluidLayout(true);166$are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));167foreach ($are_buttons as $column) {168$button_columns->addColumn($column);169}170171$out[] = phutil_tag(172'div',173array(174'class' => 'phabricator-login-buttons',175),176$button_columns);177}178179$invite_message = null;180if ($invite) {181$invite_message = $this->renderInviteHeader($invite);182}183184$custom_message = $this->newCustomStartMessage();185186$email_login = $this->newEmailLoginView($configs);187188$crumbs = $this->buildApplicationCrumbs();189$crumbs->addTextCrumb(pht('Login'));190$crumbs->setBorder(true);191192$title = pht('Login');193$view = array(194$invite_message,195$custom_message,196$out,197$email_login,198);199200return $this->newPage()201->setTitle($title)202->setCrumbs($crumbs)203->appendChild($view);204}205206207private function processAjaxRequest() {208$request = $this->getRequest();209$viewer = $request->getUser();210211// We end up here if the user clicks a workflow link that they need to212// login to use. We give them a dialog saying "You need to login...".213214if ($request->isDialogFormPost()) {215return id(new AphrontRedirectResponse())->setURI(216$request->getRequestURI());217}218219// Often, users end up here by clicking a disabled action link in the UI220// (for example, they might click "Edit Subtasks" on a Maniphest task221// page). After they log in we want to send them back to that main object222// page if we can, since it's confusing to end up on a standalone page with223// only a dialog (particularly if that dialog is another error,224// like a policy exception).225226$via_header = AphrontRequest::getViaHeaderName();227$via_uri = AphrontRequest::getHTTPHeader($via_header);228if ($via_uri !== null && strlen($via_uri)) {229PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true);230}231232return $this->newDialog()233->setTitle(pht('Login Required'))234->appendParagraph(pht('You must log in to take this action.'))235->addSubmitButton(pht('Log In'))236->addCancelButton('/');237}238239240private function processConduitRequest() {241$request = $this->getRequest();242$viewer = $request->getUser();243244// A common source of errors in Conduit client configuration is getting245// the request path wrong. The client will end up here, so make some246// effort to give them a comprehensible error message.247248$request_path = $this->getRequest()->getPath();249$conduit_path = '/api/<method>';250$example_path = '/api/conduit.ping';251252$message = pht(253'ERROR: You are making a Conduit API request to "%s", but the correct '.254'HTTP request path to use in order to access a Conduit method is "%s" '.255'(for example, "%s"). Check your configuration.',256$request_path,257$conduit_path,258$example_path);259260return id(new AphrontPlainTextResponse())->setContent($message);261}262263protected function renderError($message) {264return $this->renderErrorPage(265pht('Authentication Failure'),266array($message));267}268269private function tryAutoLogin(array $providers) {270$request = $this->getRequest();271272// If the user just logged out, don't immediately log them in again.273if ($request->getURIData('loggedout')) {274return null;275}276277// If we have more than one provider, we can't autologin because we278// don't know which one the user wants.279if (count($providers) != 1) {280return null;281}282283$provider = head($providers);284if (!$provider->supportsAutoLogin()) {285return null;286}287288$config = $provider->getProviderConfig();289if (!$config->getShouldAutoLogin()) {290return null;291}292293$auto_uri = $provider->getAutoLoginURI($request);294295return id(new AphrontRedirectResponse())296->setIsExternal(true)297->setURI($auto_uri);298}299300private function newEmailLoginView(array $configs) {301assert_instances_of($configs, 'PhabricatorAuthProviderConfig');302303// Check if password auth is enabled. If it is, the password login form304// renders a "Forgot password?" link, so we don't need to provide a305// supplemental link.306307$has_password = false;308foreach ($configs as $config) {309$provider = $config->getProvider();310if ($provider instanceof PhabricatorPasswordAuthProvider) {311$has_password = true;312}313}314315if ($has_password) {316return null;317}318319$view = array(320pht('Trouble logging in?'),321' ',322phutil_tag(323'a',324array(325'href' => '/login/email/',326),327pht('Send a login link to your email address.')),328);329330return phutil_tag(331'div',332array(333'class' => 'auth-custom-message',334),335$view);336}337338339}340341342