diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 4ac23b0ef5..16f8136943 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -814,7 +814,7 @@ celerity_register_resource_map(array( ), 'aphront-form-view-css' => array( - 'uri' => '/res/4fe4c174/rsrc/css/aphront/form-view.css', + 'uri' => '/res/a668dc36/rsrc/css/aphront/form-view.css', 'type' => 'css', 'requires' => array( @@ -941,7 +941,7 @@ celerity_register_resource_map(array( ), 'conpherence-message-pane-css' => array( - 'uri' => '/res/a3cb0efc/rsrc/css/application/conpherence/message-pane.css', + 'uri' => '/res/80cde760/rsrc/css/application/conpherence/message-pane.css', 'type' => 'css', 'requires' => array( @@ -959,7 +959,7 @@ celerity_register_resource_map(array( ), 'conpherence-widget-pane-css' => array( - 'uri' => '/res/d1db8220/rsrc/css/application/conpherence/widget-pane.css', + 'uri' => '/res/ee9d5004/rsrc/css/application/conpherence/widget-pane.css', 'type' => 'css', 'requires' => array( @@ -3601,7 +3601,7 @@ celerity_register_resource_map(array( ), 'phabricator-zindex-css' => array( - 'uri' => '/res/f919f9e1/rsrc/css/core/z-index.css', + 'uri' => '/res/99a9447b/rsrc/css/core/z-index.css', 'type' => 'css', 'requires' => array( @@ -4048,7 +4048,7 @@ celerity_register_resource_map(array( ), array( 'packages' => array( - '0a101340' => + 'ab82f2c0' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -4096,7 +4096,7 @@ celerity_register_resource_map(array( 40 => 'phabricator-property-list-view-css', 41 => 'phabricator-tag-view-css', ), - 'uri' => '/res/pkg/0a101340/core.pkg.css', + 'uri' => '/res/pkg/ab82f2c0/core.pkg.css', 'type' => 'css', ), 'f2ad0683' => @@ -4290,16 +4290,16 @@ celerity_register_resource_map(array( 'reverse' => array( 'aphront-attached-file-view-css' => 'a7ca34a9', - 'aphront-dialog-view-css' => '0a101340', - 'aphront-error-view-css' => '0a101340', - 'aphront-form-view-css' => '0a101340', - 'aphront-list-filter-view-css' => '0a101340', - 'aphront-pager-view-css' => '0a101340', - 'aphront-panel-view-css' => '0a101340', - 'aphront-table-view-css' => '0a101340', - 'aphront-tokenizer-control-css' => '0a101340', - 'aphront-tooltip-css' => '0a101340', - 'aphront-typeahead-control-css' => '0a101340', + 'aphront-dialog-view-css' => 'ab82f2c0', + 'aphront-error-view-css' => 'ab82f2c0', + 'aphront-form-view-css' => 'ab82f2c0', + 'aphront-list-filter-view-css' => 'ab82f2c0', + 'aphront-pager-view-css' => 'ab82f2c0', + 'aphront-panel-view-css' => 'ab82f2c0', + 'aphront-table-view-css' => 'ab82f2c0', + 'aphront-tokenizer-control-css' => 'ab82f2c0', + 'aphront-tooltip-css' => 'ab82f2c0', + 'aphront-typeahead-control-css' => 'ab82f2c0', 'differential-changeset-view-css' => 'dd27a69b', 'differential-core-view-css' => 'dd27a69b', 'differential-inline-comment-editor' => '9488bb69', @@ -4313,7 +4313,7 @@ celerity_register_resource_map(array( 'differential-table-of-contents-css' => 'dd27a69b', 'diffusion-commit-view-css' => 'c8ce2d88', 'diffusion-icons-css' => 'c8ce2d88', - 'global-drag-and-drop-css' => '0a101340', + 'global-drag-and-drop-css' => 'ab82f2c0', 'inline-comment-summary-css' => 'dd27a69b', 'javelin-aphlict' => 'f2ad0683', 'javelin-behavior' => 'a9f14d76', @@ -4387,55 +4387,55 @@ celerity_register_resource_map(array( 'javelin-util' => 'a9f14d76', 'javelin-vector' => 'a9f14d76', 'javelin-workflow' => 'a9f14d76', - 'lightbox-attachment-css' => '0a101340', + 'lightbox-attachment-css' => 'ab82f2c0', 'maniphest-task-summary-css' => 'a7ca34a9', 'maniphest-transaction-detail-css' => 'a7ca34a9', - 'phabricator-action-list-view-css' => '0a101340', - 'phabricator-application-launch-view-css' => '0a101340', + 'phabricator-action-list-view-css' => 'ab82f2c0', + 'phabricator-application-launch-view-css' => 'ab82f2c0', 'phabricator-busy' => 'f2ad0683', 'phabricator-content-source-view-css' => 'dd27a69b', - 'phabricator-core-css' => '0a101340', - 'phabricator-crumbs-view-css' => '0a101340', + 'phabricator-core-css' => 'ab82f2c0', + 'phabricator-crumbs-view-css' => 'ab82f2c0', 'phabricator-drag-and-drop-file-upload' => '9488bb69', 'phabricator-dropdown-menu' => 'f2ad0683', 'phabricator-file-upload' => 'f2ad0683', - 'phabricator-filetree-view-css' => '0a101340', - 'phabricator-flag-css' => '0a101340', - 'phabricator-form-view-css' => '0a101340', - 'phabricator-header-view-css' => '0a101340', + 'phabricator-filetree-view-css' => 'ab82f2c0', + 'phabricator-flag-css' => 'ab82f2c0', + 'phabricator-form-view-css' => 'ab82f2c0', + 'phabricator-header-view-css' => 'ab82f2c0', 'phabricator-hovercard' => 'f2ad0683', - 'phabricator-jump-nav' => '0a101340', + 'phabricator-jump-nav' => 'ab82f2c0', 'phabricator-keyboard-shortcut' => 'f2ad0683', 'phabricator-keyboard-shortcut-manager' => 'f2ad0683', - 'phabricator-main-menu-view' => '0a101340', + 'phabricator-main-menu-view' => 'ab82f2c0', 'phabricator-menu-item' => 'f2ad0683', - 'phabricator-nav-view-css' => '0a101340', + 'phabricator-nav-view-css' => 'ab82f2c0', 'phabricator-notification' => 'f2ad0683', - 'phabricator-notification-css' => '0a101340', - 'phabricator-notification-menu-css' => '0a101340', - 'phabricator-object-item-list-view-css' => '0a101340', + 'phabricator-notification-css' => 'ab82f2c0', + 'phabricator-notification-menu-css' => 'ab82f2c0', + 'phabricator-object-item-list-view-css' => 'ab82f2c0', 'phabricator-object-selector-css' => 'dd27a69b', 'phabricator-phtize' => 'f2ad0683', 'phabricator-prefab' => 'f2ad0683', 'phabricator-project-tag-css' => 'a7ca34a9', - 'phabricator-property-list-view-css' => '0a101340', - 'phabricator-remarkup-css' => '0a101340', + 'phabricator-property-list-view-css' => 'ab82f2c0', + 'phabricator-remarkup-css' => 'ab82f2c0', 'phabricator-shaped-request' => '9488bb69', - 'phabricator-side-menu-view-css' => '0a101340', - 'phabricator-standard-page-view' => '0a101340', - 'phabricator-tag-view-css' => '0a101340', + 'phabricator-side-menu-view-css' => 'ab82f2c0', + 'phabricator-standard-page-view' => 'ab82f2c0', + 'phabricator-tag-view-css' => 'ab82f2c0', 'phabricator-textareautils' => 'f2ad0683', 'phabricator-tooltip' => 'f2ad0683', - 'phabricator-transaction-view-css' => '0a101340', - 'phabricator-zindex-css' => '0a101340', - 'phui-button-css' => '0a101340', - 'phui-form-css' => '0a101340', - 'phui-icon-view-css' => '0a101340', - 'phui-spacing-css' => '0a101340', - 'sprite-apps-large-css' => '0a101340', - 'sprite-gradient-css' => '0a101340', - 'sprite-icons-css' => '0a101340', - 'sprite-menu-css' => '0a101340', - 'syntax-highlighting-css' => '0a101340', + 'phabricator-transaction-view-css' => 'ab82f2c0', + 'phabricator-zindex-css' => 'ab82f2c0', + 'phui-button-css' => 'ab82f2c0', + 'phui-form-css' => 'ab82f2c0', + 'phui-icon-view-css' => 'ab82f2c0', + 'phui-spacing-css' => 'ab82f2c0', + 'sprite-apps-large-css' => 'ab82f2c0', + 'sprite-gradient-css' => 'ab82f2c0', + 'sprite-icons-css' => 'ab82f2c0', + 'sprite-menu-css' => 'ab82f2c0', + 'syntax-highlighting-css' => 'ab82f2c0', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4ac1d86c56..aa62f7d0f3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -819,6 +819,7 @@ phutil_register_library_map(array( 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 'PhabricatorAuthProviderOAuth' => 'applications/auth/provider/PhabricatorAuthProviderOAuth.php', 'PhabricatorAuthProviderOAuthFacebook' => 'applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php', + 'PhabricatorAuthProviderPassword' => 'applications/auth/provider/PhabricatorAuthProviderPassword.php', 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', @@ -2678,6 +2679,7 @@ phutil_register_library_map(array( 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthProviderOAuth' => 'PhabricatorAuthProvider', 'PhabricatorAuthProviderOAuthFacebook' => 'PhabricatorAuthProviderOAuth', + 'PhabricatorAuthProviderPassword' => 'PhabricatorAuthProvider', 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', diff --git a/src/applications/auth/application/PhabricatorApplicationAuth.php b/src/applications/auth/application/PhabricatorApplicationAuth.php index 38d92b9b49..68a3bd3177 100644 --- a/src/applications/auth/application/PhabricatorApplicationAuth.php +++ b/src/applications/auth/application/PhabricatorApplicationAuth.php @@ -37,7 +37,7 @@ final class PhabricatorApplicationAuth extends PhabricatorApplication { return array( '/auth/' => array( 'login/(?P[^/]+)/' => 'PhabricatorAuthLoginController', - 'register/(?P[^/]+)/' => 'PhabricatorAuthRegisterController', + 'register/(?:(?P[^/]+)/)?' => 'PhabricatorAuthRegisterController', 'start/' => 'PhabricatorAuthStartController', 'validate/' => 'PhabricatorAuthValidateController', ), diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php index bf6cc3605a..01fa1ef159 100644 --- a/src/applications/auth/controller/PhabricatorAuthLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php @@ -30,6 +30,11 @@ final class PhabricatorAuthLoginController return $response; } + if (!$account) { + throw new Exception( + "Auth provider failed to load an account from processLoginRequest()!"); + } + if ($account->getUserPHID()) { // The account is already attached to a Phabricator user, so this is // either a login or a bad account link request. @@ -170,6 +175,31 @@ final class PhabricatorAuthLoginController )); } + public function buildProviderPageResponse( + PhabricatorAuthProvider $provider, + $content) { + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Login')) + ->setHref($this->getApplicationURI('start/'))); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName($provider->getProviderName())); + + return $this->buildApplicationPage( + array( + $crumbs, + $content, + ), + array( + 'title' => pht('Login'), + 'device' => true, + 'dust' => true, + )); + } + public function buildProviderErrorResponse( PhabricatorAuthProvider $provider, $message) { diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 86fa4071dd..c9cccfe4d7 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -74,6 +74,10 @@ abstract class PhabricatorAuthProvider { return; } + protected function willRegisterAccount(PhabricatorExternalAccount $account) { + return; + } + protected function loadOrCreateAccount($account_id) { if (!strlen($account_id)) { throw new Exception( @@ -107,7 +111,6 @@ abstract class PhabricatorAuthProvider { ->setAccountID($account_id); } - $account->setDisplayName(''); $account->setUsername($adapter->getAccountName()); $account->setRealName($adapter->getAccountRealName()); $account->setEmail($adapter->getAccountEmail()); @@ -147,4 +150,16 @@ abstract class PhabricatorAuthProvider { return PhabricatorEnv::getURI($uri); } + public function isDefaultRegistrationProvider() { + return false; + } + + public function shouldRequireRegistrationPassword() { + return false; + } + + public function getDefaultExternalAccount() { + throw new Exception("Not implemented!"); + } + } diff --git a/src/applications/auth/provider/PhabricatorAuthProviderPassword.php b/src/applications/auth/provider/PhabricatorAuthProviderPassword.php new file mode 100644 index 0000000000..4450e8324a --- /dev/null +++ b/src/applications/auth/provider/PhabricatorAuthProviderPassword.php @@ -0,0 +1,231 @@ +adapter) { + $adapter = new PhutilAuthAdapterEmpty(); + $adapter->setAdapterType('password'); + $adapter->setAdapterDomain('self'); + $this->adapter = $adapter; + } + return $this->adapter; + } + + public function shouldAllowLogin() { + return true; + } + + public function shouldAllowRegistration() { + return true; + } + + public function shouldAllowAccountLink() { + return false; + } + + public function shouldAllowAccountUnlink() { + return false; + } + + public function isDefaultRegistrationProvider() { + return true; + } + + public function buildLoginForm( + PhabricatorAuthStartController $controller) { + + $request = $controller->getRequest(); + + return $this->renderLoginForm($request); + } + + private function renderLoginForm( + AphrontRequest $request, + $require_captcha = false, + $captcha_valid = false) { + + $viewer = $request->getUser(); + + $submit = id(new AphrontFormSubmitControl()) + ->setValue(pht('Login')); + + if ($this->shouldAllowRegistration()) { + $submit->addCancelButton( + '/auth/register/', + pht('Register New Account')); + } + + $header = id(new PhabricatorHeaderView()) + ->setHeader(pht('Login to Phabricator')); + + $v_user = nonempty( + $request->getStr('username'), + $request->getCookie('phusr')); + + $e_user = null; + $e_pass = null; + $e_captcha = null; + + $errors = array(); + if ($require_captcha && !$captcha_valid) { + if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) { + $e_captcha = pht('Invalid'); + $errors[] = pht('CAPTCHA was not entered correctly.'); + } else { + $e_captcha = pht('Required'); + $errors[] = pht('Too many login failures recently. You must '. + 'submit a CAPTCHA with your login request.'); + } + } else if ($request->isHTTPPost()) { + // NOTE: This is intentionally vague so as not to disclose whether a + // given username or email is registered. + $e_user = pht('Invalid'); + $e_pass = pht('Invalid'); + $errors[] = pht('Username or password are incorrect.'); + } + + $form = id(new AphrontFormView()) + ->setAction($this->getLoginURI()) + ->setUser($viewer) + ->setFlexible(true) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Username/Email') + ->setName('username') + ->setValue($v_user) + ->setError($e_user)) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setLabel('Password') + ->setName('password') + ->setError($e_pass) + ->setCaption( + phutil_tag( + 'a', + array( + 'href' => '/login/email/', + ), + pht('Forgot your password?')))); + + if ($require_captcha) { + $form->appendChild( + id(new AphrontFormRecaptchaControl()) + ->setError($e_captcha)); + } + + $form + ->appendChild($submit); + + if ($errors) { + $errors = id(new AphrontErrorView())->setErrors($errors); + } + + return array( + $errors, + $header, + $form, + ); + } + + public function processLoginRequest( + PhabricatorAuthLoginController $controller) { + + $request = $controller->getRequest(); + $viewer = $request->getUser(); + + $require_captcha = false; + $captcha_valid = false; + if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) { + $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( + PhabricatorUserLog::ACTION_LOGIN_FAILURE, + 60 * 15); + if (count($failed_attempts) > 5) { + $require_captcha = true; + $captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request); + } + } + + $response = null; + $account = null; + $log_user = null; + + if (!$require_captcha || $captcha_valid) { + $username_or_email = $request->getStr('username'); + + if (strlen($username_or_email)) { + $user = id(new PhabricatorUser())->loadOneWhere( + 'username = %s', + $username_or_email); + + if (!$user) { + $user = PhabricatorUser::loadOneWithEmailAddress($username_or_email); + } + } + + if ($user) { + $envelope = new PhutilOpaqueEnvelope($request->getStr('password')); + if ($user->comparePassword($envelope)) { + $account = $this->loadOrCreateAccount($user->getPHID()); + $log_user = $user; + } + } + } + + if (!$account) { + $log = PhabricatorUserLog::newLog( + null, + $log_user, + PhabricatorUserLog::ACTION_LOGIN_FAILURE); + $log->save(); + + $request->clearCookie('phusr'); + $request->clearCookie('phsid'); + + $response = $controller->buildProviderPageResponse( + $this, + $this->renderLoginForm( + $request, + $require_captcha, + $captcha_valid)); + } + + return array($account, $response); + } + + public function shouldRequireRegistrationPassword() { + return true; + } + + public function getDefaultExternalAccount() { + $adapter = $this->getAdapter(); + + return id(new PhabricatorExternalAccount()) + ->setAccountType($adapter->getAdapterType()) + ->setAccountDomain($adapter->getAdapterDomain()); + } + + protected function willSaveAccount(PhabricatorExternalAccount $account) { + parent::willSaveAccount($account); + $account->setUserPHID($account->getAccountID()); + } + + public function willRegisterAccount(PhabricatorExternalAccount $account) { + parent::willRegisterAccount($account); + $account->setAccountID($account->getUserPHID()); + } + +} diff --git a/webroot/rsrc/css/aphront/form-view.css b/webroot/rsrc/css/aphront/form-view.css index 8f0c6f8f96..3969b126b1 100644 --- a/webroot/rsrc/css/aphront/form-view.css +++ b/webroot/rsrc/css/aphront/form-view.css @@ -437,3 +437,7 @@ table.aphront-form-control-checkbox-layout th { width: 85%; margin: 15px auto; } + +.recaptcha_only_if_privacy { + display: none; +}