Skip to content

Commit 7f11e8d

Browse files
author
epriestley
committed
Improve handling of email verification and "activated" accounts
Summary: Small step forward which improves existing stuff or lays groudwork for future stuff: - Currently, to check for email verification, we have to single-query the email address on every page. Instead, denoramlize it into the user object. - Migrate all the existing users. - When the user verifies an email, mark them as `isEmailVerified` if the email is their primary email. - Just make the checks look at the `isEmailVerified` field. - Add a new check, `isUserActivated()`, to cover email-verified plus disabled. Currently, a non-verified-but-not-disabled user could theoretically use Conduit over SSH, if anyone deployed it. Tighten that up. - Add an `isApproved` flag, which is always true for now. In a future diff, I want to add a default-on admin approval queue for new accounts, to prevent configuration mistakes. The way it will work is: - When the queue is enabled, registering users are created with `isApproved = false`. - Admins are sent an email, "[Phabricator] New User Approval (alincoln)", telling them that a new user is waiting for approval. - They go to the web UI and approve the user. - Manually-created accounts are auto-approved. - The email will have instructions for disabling the queue. I think this queue will be helpful for new installs and give them peace of mind, and when you go to disable it we have a better opportunity to warn you about exactly what that means. Generally, I want to improve the default safety of registration, since if you just blindly coast through the path of least resistance right now your install ends up pretty open, and realistically few installs are on VPNs. Test Plan: - Ran migration, verified `isEmailVerified` populated correctly. - Created a new user, checked DB for verified (not verified). - Verified, checked DB (now verified). - Used Conduit, People, Diffusion. Reviewers: btrahan Reviewed By: btrahan CC: chad, aran Differential Revision: https://secure.phabricator.com/D7572
1 parent cd73fe7 commit 7f11e8d

27 files changed

+237
-123
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ALTER TABLE {$NAMESPACE}_user.user
2+
ADD isEmailVerified INT UNSIGNED NOT NULL;
3+
4+
ALTER TABLE {$NAMESPACE}_user.user
5+
ADD isApproved INT UNSIGNED NOT NULL;
6+
7+
ALTER TABLE {$NAMESPACE}_user.user
8+
ADD KEY `key_approved` (isApproved);
9+
10+
UPDATE {$NAMESPACE}_user.user SET isApproved = 1;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
$table = new PhabricatorUser();
4+
$conn_w = $table->establishConnection('w');
5+
6+
foreach (new LiskMigrationIterator($table) as $user) {
7+
$username = $user->getUsername();
8+
echo "Migrating {$username}...\n";
9+
if ($user->getIsEmailVerified()) {
10+
// Email already verified.
11+
continue;
12+
}
13+
14+
$primary = $user->loadPrimaryEmail();
15+
if (!$primary) {
16+
// No primary email.
17+
continue;
18+
}
19+
20+
if (!$primary->getIsVerified()) {
21+
// Primary email not verified.
22+
continue;
23+
}
24+
25+
// Primary email is verified, so mark the account as verified.
26+
queryfx(
27+
$conn_w,
28+
'UPDATE %T SET isEmailVerified = 1 WHERE id = %d',
29+
$table->getTableName(),
30+
$user->getID());
31+
}
32+
33+
echo "Done.\n";

scripts/ssh/ssh-exec.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
throw new Exception("Invalid username.");
3939
}
4040

41-
if ($user->getIsDisabled()) {
42-
throw new Exception("You have been exiled.");
41+
if (!$user->isUserActivated()) {
42+
throw new Exception(pht("Your account is not activated."));
4343
}
4444

4545
if ($args->getArg('ssh-command')) {

src/__celerity_resource_map__.php

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@
857857
),
858858
'aphront-dialog-view-css' =>
859859
array(
860-
'uri' => '/res/830fa2de/rsrc/css/aphront/dialog-view.css',
860+
'uri' => '/res/6b6a41c6/rsrc/css/aphront/dialog-view.css',
861861
'type' => 'css',
862862
'requires' =>
863863
array(
@@ -1889,7 +1889,7 @@
18891889
),
18901890
'javelin-behavior-maniphest-batch-selector' =>
18911891
array(
1892-
'uri' => '/res/423c5f1b/rsrc/js/application/maniphest/behavior-batch-selector.js',
1892+
'uri' => '/res/a82658b3/rsrc/js/application/maniphest/behavior-batch-selector.js',
18931893
'type' => 'js',
18941894
'requires' =>
18951895
array(
@@ -1917,7 +1917,7 @@
19171917
),
19181918
'javelin-behavior-maniphest-subpriority-editor' =>
19191919
array(
1920-
'uri' => '/res/1fa4961f/rsrc/js/application/maniphest/behavior-subpriorityeditor.js',
1920+
'uri' => '/res/95f3d4a6/rsrc/js/application/maniphest/behavior-subpriorityeditor.js',
19211921
'type' => 'js',
19221922
'requires' =>
19231923
array(
@@ -4328,7 +4328,7 @@
43284328
), array(
43294329
'packages' =>
43304330
array(
4331-
'f0d63822' =>
4331+
'd831cac3' =>
43324332
array(
43334333
'name' => 'core.pkg.css',
43344334
'symbols' =>
@@ -4377,7 +4377,7 @@
43774377
41 => 'phabricator-tag-view-css',
43784378
42 => 'phui-list-view-css',
43794379
),
4380-
'uri' => '/res/pkg/f0d63822/core.pkg.css',
4380+
'uri' => '/res/pkg/d831cac3/core.pkg.css',
43814381
'type' => 'css',
43824382
),
43834383
'2c1dba03' =>
@@ -4552,7 +4552,7 @@
45524552
'uri' => '/res/pkg/49898640/maniphest.pkg.css',
45534553
'type' => 'css',
45544554
),
4555-
'0a694954' =>
4555+
'0474f45c' =>
45564556
array(
45574557
'name' => 'maniphest.pkg.js',
45584558
'symbols' =>
@@ -4563,21 +4563,21 @@
45634563
3 => 'javelin-behavior-maniphest-transaction-expand',
45644564
4 => 'javelin-behavior-maniphest-subpriority-editor',
45654565
),
4566-
'uri' => '/res/pkg/0a694954/maniphest.pkg.js',
4566+
'uri' => '/res/pkg/0474f45c/maniphest.pkg.js',
45674567
'type' => 'js',
45684568
),
45694569
),
45704570
'reverse' =>
45714571
array(
4572-
'aphront-dialog-view-css' => 'f0d63822',
4573-
'aphront-error-view-css' => 'f0d63822',
4574-
'aphront-list-filter-view-css' => 'f0d63822',
4575-
'aphront-pager-view-css' => 'f0d63822',
4576-
'aphront-panel-view-css' => 'f0d63822',
4577-
'aphront-table-view-css' => 'f0d63822',
4578-
'aphront-tokenizer-control-css' => 'f0d63822',
4579-
'aphront-tooltip-css' => 'f0d63822',
4580-
'aphront-typeahead-control-css' => 'f0d63822',
4572+
'aphront-dialog-view-css' => 'd831cac3',
4573+
'aphront-error-view-css' => 'd831cac3',
4574+
'aphront-list-filter-view-css' => 'd831cac3',
4575+
'aphront-pager-view-css' => 'd831cac3',
4576+
'aphront-panel-view-css' => 'd831cac3',
4577+
'aphront-table-view-css' => 'd831cac3',
4578+
'aphront-tokenizer-control-css' => 'd831cac3',
4579+
'aphront-tooltip-css' => 'd831cac3',
4580+
'aphront-typeahead-control-css' => 'd831cac3',
45814581
'differential-changeset-view-css' => '1084b12b',
45824582
'differential-core-view-css' => '1084b12b',
45834583
'differential-inline-comment-editor' => '5e9e5c4e',
@@ -4591,7 +4591,7 @@
45914591
'differential-table-of-contents-css' => '1084b12b',
45924592
'diffusion-commit-view-css' => '7aa115b4',
45934593
'diffusion-icons-css' => '7aa115b4',
4594-
'global-drag-and-drop-css' => 'f0d63822',
4594+
'global-drag-and-drop-css' => 'd831cac3',
45954595
'inline-comment-summary-css' => '1084b12b',
45964596
'javelin-aphlict' => '2c1dba03',
45974597
'javelin-behavior' => '3e3be199',
@@ -4623,11 +4623,11 @@
46234623
'javelin-behavior-konami' => '2c1dba03',
46244624
'javelin-behavior-lightbox-attachments' => '2c1dba03',
46254625
'javelin-behavior-load-blame' => '5e9e5c4e',
4626-
'javelin-behavior-maniphest-batch-selector' => '0a694954',
4627-
'javelin-behavior-maniphest-subpriority-editor' => '0a694954',
4628-
'javelin-behavior-maniphest-transaction-controls' => '0a694954',
4629-
'javelin-behavior-maniphest-transaction-expand' => '0a694954',
4630-
'javelin-behavior-maniphest-transaction-preview' => '0a694954',
4626+
'javelin-behavior-maniphest-batch-selector' => '0474f45c',
4627+
'javelin-behavior-maniphest-subpriority-editor' => '0474f45c',
4628+
'javelin-behavior-maniphest-transaction-controls' => '0474f45c',
4629+
'javelin-behavior-maniphest-transaction-expand' => '0474f45c',
4630+
'javelin-behavior-maniphest-transaction-preview' => '0474f45c',
46314631
'javelin-behavior-phabricator-active-nav' => '2c1dba03',
46324632
'javelin-behavior-phabricator-autofocus' => '2c1dba03',
46334633
'javelin-behavior-phabricator-gesture' => '2c1dba03',
@@ -4666,56 +4666,56 @@
46664666
'javelin-util' => '3e3be199',
46674667
'javelin-vector' => '3e3be199',
46684668
'javelin-workflow' => '3e3be199',
4669-
'lightbox-attachment-css' => 'f0d63822',
4669+
'lightbox-attachment-css' => 'd831cac3',
46704670
'maniphest-task-summary-css' => '49898640',
4671-
'phabricator-action-list-view-css' => 'f0d63822',
4672-
'phabricator-application-launch-view-css' => 'f0d63822',
4671+
'phabricator-action-list-view-css' => 'd831cac3',
4672+
'phabricator-application-launch-view-css' => 'd831cac3',
46734673
'phabricator-busy' => '2c1dba03',
46744674
'phabricator-content-source-view-css' => '1084b12b',
4675-
'phabricator-core-css' => 'f0d63822',
4676-
'phabricator-crumbs-view-css' => 'f0d63822',
4675+
'phabricator-core-css' => 'd831cac3',
4676+
'phabricator-crumbs-view-css' => 'd831cac3',
46774677
'phabricator-drag-and-drop-file-upload' => '5e9e5c4e',
46784678
'phabricator-dropdown-menu' => '2c1dba03',
46794679
'phabricator-file-upload' => '2c1dba03',
4680-
'phabricator-filetree-view-css' => 'f0d63822',
4681-
'phabricator-flag-css' => 'f0d63822',
4680+
'phabricator-filetree-view-css' => 'd831cac3',
4681+
'phabricator-flag-css' => 'd831cac3',
46824682
'phabricator-hovercard' => '2c1dba03',
4683-
'phabricator-jump-nav' => 'f0d63822',
4683+
'phabricator-jump-nav' => 'd831cac3',
46844684
'phabricator-keyboard-shortcut' => '2c1dba03',
46854685
'phabricator-keyboard-shortcut-manager' => '2c1dba03',
4686-
'phabricator-main-menu-view' => 'f0d63822',
4686+
'phabricator-main-menu-view' => 'd831cac3',
46874687
'phabricator-menu-item' => '2c1dba03',
4688-
'phabricator-nav-view-css' => 'f0d63822',
4688+
'phabricator-nav-view-css' => 'd831cac3',
46894689
'phabricator-notification' => '2c1dba03',
4690-
'phabricator-notification-css' => 'f0d63822',
4691-
'phabricator-notification-menu-css' => 'f0d63822',
4690+
'phabricator-notification-css' => 'd831cac3',
4691+
'phabricator-notification-menu-css' => 'd831cac3',
46924692
'phabricator-object-selector-css' => '1084b12b',
46934693
'phabricator-phtize' => '2c1dba03',
46944694
'phabricator-prefab' => '2c1dba03',
46954695
'phabricator-project-tag-css' => '49898640',
4696-
'phabricator-remarkup-css' => 'f0d63822',
4696+
'phabricator-remarkup-css' => 'd831cac3',
46974697
'phabricator-shaped-request' => '5e9e5c4e',
4698-
'phabricator-side-menu-view-css' => 'f0d63822',
4699-
'phabricator-standard-page-view' => 'f0d63822',
4700-
'phabricator-tag-view-css' => 'f0d63822',
4698+
'phabricator-side-menu-view-css' => 'd831cac3',
4699+
'phabricator-standard-page-view' => 'd831cac3',
4700+
'phabricator-tag-view-css' => 'd831cac3',
47014701
'phabricator-textareautils' => '2c1dba03',
47024702
'phabricator-tooltip' => '2c1dba03',
4703-
'phabricator-transaction-view-css' => 'f0d63822',
4704-
'phabricator-zindex-css' => 'f0d63822',
4705-
'phui-button-css' => 'f0d63822',
4706-
'phui-form-css' => 'f0d63822',
4707-
'phui-form-view-css' => 'f0d63822',
4708-
'phui-header-view-css' => 'f0d63822',
4709-
'phui-icon-view-css' => 'f0d63822',
4710-
'phui-list-view-css' => 'f0d63822',
4711-
'phui-object-item-list-view-css' => 'f0d63822',
4712-
'phui-property-list-view-css' => 'f0d63822',
4713-
'phui-spacing-css' => 'f0d63822',
4714-
'sprite-apps-large-css' => 'f0d63822',
4715-
'sprite-gradient-css' => 'f0d63822',
4716-
'sprite-icons-css' => 'f0d63822',
4717-
'sprite-menu-css' => 'f0d63822',
4718-
'sprite-status-css' => 'f0d63822',
4719-
'syntax-highlighting-css' => 'f0d63822',
4703+
'phabricator-transaction-view-css' => 'd831cac3',
4704+
'phabricator-zindex-css' => 'd831cac3',
4705+
'phui-button-css' => 'd831cac3',
4706+
'phui-form-css' => 'd831cac3',
4707+
'phui-form-view-css' => 'd831cac3',
4708+
'phui-header-view-css' => 'd831cac3',
4709+
'phui-icon-view-css' => 'd831cac3',
4710+
'phui-list-view-css' => 'd831cac3',
4711+
'phui-object-item-list-view-css' => 'd831cac3',
4712+
'phui-property-list-view-css' => 'd831cac3',
4713+
'phui-spacing-css' => 'd831cac3',
4714+
'sprite-apps-large-css' => 'd831cac3',
4715+
'sprite-gradient-css' => 'd831cac3',
4716+
'sprite-icons-css' => 'd831cac3',
4717+
'sprite-menu-css' => 'd831cac3',
4718+
'sprite-status-css' => 'd831cac3',
4719+
'syntax-highlighting-css' => 'd831cac3',
47204720
),
47214721
));

src/applications/auth/controller/PhabricatorEmailVerificationController.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,20 @@ public function processRequest() {
3939
$continue = pht('Continue to Phabricator');
4040
} else {
4141
$guard = AphrontWriteGuard::beginScopedUnguardedWrites();
42-
$email->setIsVerified(1);
43-
$email->save();
42+
$email->openTransaction();
43+
44+
$email->setIsVerified(1);
45+
$email->save();
46+
47+
// If the user just verified their primary email address, mark their
48+
// account as email verified.
49+
$user_primary = $user->loadPrimaryEmail();
50+
if ($user_primary->getID() == $email->getID()) {
51+
$user->setIsEmailVerified(1);
52+
$user->save();
53+
}
54+
55+
$email->saveTransaction();
4456
unset($guard);
4557

4658
$title = pht('Address Verified');

src/applications/auth/controller/PhabricatorMustVerifyEmailController.php

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,42 +31,33 @@ public function processRequest() {
3131
$sent = new AphrontErrorView();
3232
$sent->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
3333
$sent->setTitle(pht('Email Sent'));
34-
$sent->appendChild(phutil_tag(
35-
'p',
36-
array(),
34+
$sent->appendChild(
3735
pht(
3836
'Another verification email was sent to %s.',
39-
phutil_tag('strong', array(), $email_address))));
37+
phutil_tag('strong', array(), $email_address)));
4038
}
4139

42-
$error_view = new AphrontRequestFailureView();
43-
$error_view->setHeader(pht('Check Your Email'));
44-
$error_view->appendChild(phutil_tag('p', array(), pht(
45-
'You must verify your email address to login. You should have a new '.
46-
'email message from Phabricator with verification instructions in your '.
47-
'inbox (%s).', phutil_tag('strong', array(), $email_address))));
48-
$error_view->appendChild(phutil_tag('p', array(), pht(
40+
$must_verify = pht(
41+
'You must verify your email address to login. You should have a '.
42+
'new email message from Phabricator with verification instructions '.
43+
'in your inbox (%s).',
44+
phutil_tag('strong', array(), $email_address));
45+
46+
$send_again = pht(
4947
'If you did not receive an email, you can click the button below '.
50-
'to try sending another one.')));
51-
$error_view->appendChild(phutil_tag_div(
52-
'aphront-failure-continue',
53-
phabricator_form(
54-
$user,
55-
array(
56-
'action' => '/login/mustverify/',
57-
'method' => 'POST',
58-
),
59-
phutil_tag(
60-
'button',
61-
array(
62-
),
63-
pht('Send Another Email')))));
48+
'to try sending another one.');
6449

50+
$dialog = id(new AphrontDialogView())
51+
->setUser($user)
52+
->setTitle(pht('Check Your Email'))
53+
->appendParagraph($must_verify)
54+
->appendParagraph($send_again)
55+
->addSubmitButton(pht('Send Another Email'));
6556

6657
return $this->buildApplicationPage(
6758
array(
6859
$sent,
69-
$error_view,
60+
$dialog,
7061
),
7162
array(
7263
'title' => pht('Must Verify Email'),

src/applications/base/controller/PhabricatorController.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,7 @@ public function willBeginExecution() {
114114

115115
if ($user->isLoggedIn()) {
116116
if ($this->shouldRequireEmailVerification()) {
117-
$email = $user->loadPrimaryEmail();
118-
if (!$email) {
119-
throw new Exception(
120-
"No primary email address associated with this account!");
121-
}
122-
if (!$email->getIsVerified()) {
117+
if (!$user->getIsEmailVerified()) {
123118
$controller = new PhabricatorMustVerifyEmailController($request);
124119
$this->setCurrentApplication($auth_application);
125120
return $this->delegateToController($controller);

src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function testControllerAccessControls() {
3131
$u_unverified = $this->generateNewTestUser()
3232
->setUsername('unverified')
3333
->save();
34-
$u_unverified->loadPrimaryEmail()->setIsVerified(0)->save();
34+
$u_unverified->setIsEmailVerified(0)->save();
3535

3636
$u_normal = $this->generateNewTestUser()
3737
->setUsername('normal')

src/applications/conduit/controller/PhabricatorConduitAPIController.php

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -313,24 +313,11 @@ private function validateAuthenticatedUser(
313313
ConduitAPIRequest $request,
314314
PhabricatorUser $user) {
315315

316-
if ($user->getIsDisabled()) {
316+
if (!$user->isUserActivated()) {
317317
return array(
318318
'ERR-USER-DISABLED',
319-
'User is disabled.');
320-
}
321-
322-
if (PhabricatorUserEmail::isEmailVerificationRequired()) {
323-
$email = $user->loadPrimaryEmail();
324-
if (!$email) {
325-
return array(
326-
'ERR-USER-NOEMAIL',
327-
'User has no primary email address.');
328-
}
329-
if (!$email->getIsVerified()) {
330-
return array(
331-
'ERR-USER-UNVERIFIED',
332-
'User has unverified email address.');
333-
}
319+
pht('User account is not activated.'),
320+
);
334321
}
335322

336323
$request->setUser($user);

0 commit comments

Comments
 (0)