mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-01 19:22:42 +01:00
Allow users to have multiple email addresses, and verify emails
Summary: - Move email to a separate table. - Migrate existing email to new storage. - Allow users to add and remove email addresses. - Allow users to verify email addresses. - Allow users to change their primary email address. - Convert all the registration/reset/login code to understand these changes. - There are a few security considerations here but I think I've addressed them. Principally, it is important to never let a user acquire a verified email address they don't actually own. We ensure this by tightening the scoping of token generation rules to be (user, email) specific. - This should have essentially zero impact on Facebook, but may require some minor changes in the registration code -- I don't exactly remember how it is set up. Not included here (next steps): - Allow configuration to restrict email to certain domains. - Allow configuration to require validated email. Test Plan: This is a fairly extensive, difficult-to-test change. - From "Email Addresses" interface: - Added new email (verified email verifications sent). - Changed primary email (verified old/new notificactions sent). - Resent verification emails (verified they sent). - Removed email. - Tried to add already-owned email. - Created new users with "accountadmin". Edited existing users with "accountadmin". - Created new users with "add_user.php". - Created new users with web interface. - Clicked welcome email link, verified it verified email. - Reset password. - Linked/unlinked oauth accounts. - Logged in with oauth account. - Logged in with email. - Registered with Oauth account. - Tried to register with OAuth account with duplicate email. - Verified errors for email verification with bad tokens, etc. Reviewers: btrahan, vrana, jungejason Reviewed By: btrahan CC: aran Maniphest Tasks: T1184 Differential Revision: https://secure.phabricator.com/D2393
This commit is contained in:
parent
803dea1517
commit
87207b2f4e
38 changed files with 900 additions and 140 deletions
12
resources/sql/patches/emailtable.sql
Normal file
12
resources/sql/patches/emailtable.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_user.user_email (
|
||||||
|
`id` int unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
userPHID varchar(64) collate utf8_bin NOT NULL,
|
||||||
|
address varchar(128) collate utf8_general_ci NOT NULL,
|
||||||
|
isVerified bool not null default 0,
|
||||||
|
isPrimary bool not null default 0,
|
||||||
|
verificationCode varchar(64) collate utf8_bin,
|
||||||
|
dateCreated int unsigned not null,
|
||||||
|
dateModified int unsigned not null,
|
||||||
|
KEY (userPHID, isPrimary),
|
||||||
|
UNIQUE KEY (address)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
49
resources/sql/patches/emailtableport.php
Normal file
49
resources/sql/patches/emailtableport.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
echo "Migrating user emails...\n";
|
||||||
|
|
||||||
|
$table = new PhabricatorUser();
|
||||||
|
$conn = $table->establishConnection('r');
|
||||||
|
|
||||||
|
$emails = queryfx_all(
|
||||||
|
$conn,
|
||||||
|
'SELECT phid, email FROM %T',
|
||||||
|
$table->getTableName());
|
||||||
|
$emails = ipull($emails, 'email', 'phid');
|
||||||
|
|
||||||
|
$etable = new PhabricatorUserEmail();
|
||||||
|
$econn = $etable->establishConnection('w');
|
||||||
|
|
||||||
|
foreach ($emails as $phid => $email) {
|
||||||
|
|
||||||
|
// NOTE: Grandfather all existing email in as primary / verified. We generate
|
||||||
|
// verification codes because they are used for password resets, etc.
|
||||||
|
|
||||||
|
echo "Migrating '{$phid}'...\n";
|
||||||
|
queryfx(
|
||||||
|
$econn,
|
||||||
|
'INSERT INTO %T (userPHID, address, verificationCode, isVerified, isPrimary)
|
||||||
|
VALUES (%s, %s, %s, 1, 1)',
|
||||||
|
$etable->getTableName(),
|
||||||
|
$phid,
|
||||||
|
$email,
|
||||||
|
PhabricatorFile::readRandomCharacters(24));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Done.\n";
|
1
resources/sql/patches/emailtableremove.sql
Normal file
1
resources/sql/patches/emailtableremove.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_user.user DROP email;
|
|
@ -55,6 +55,8 @@ if (!$user) {
|
||||||
}
|
}
|
||||||
$user = new PhabricatorUser();
|
$user = new PhabricatorUser();
|
||||||
$user->setUsername($username);
|
$user->setUsername($username);
|
||||||
|
|
||||||
|
$is_new = true;
|
||||||
} else {
|
} else {
|
||||||
$original = clone $user;
|
$original = clone $user;
|
||||||
|
|
||||||
|
@ -66,6 +68,8 @@ if (!$user) {
|
||||||
echo "Cancelled.\n";
|
echo "Cancelled.\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$is_new = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user_realname = $user->getRealName();
|
$user_realname = $user->getRealName();
|
||||||
|
@ -79,30 +83,28 @@ $realname = nonempty(
|
||||||
$user_realname);
|
$user_realname);
|
||||||
$user->setRealName($realname);
|
$user->setRealName($realname);
|
||||||
|
|
||||||
$user_email = $user->getEmail();
|
// When creating a new user we prompt for an email address; when editing an
|
||||||
if (strlen($user_email)) {
|
// existing user we just skip this because it would be quite involved to provide
|
||||||
$email_prompt = ' ['.$user_email.']';
|
// a reasonable CLI interface for editing multiple addresses and managing email
|
||||||
} else {
|
// verification and primary addresses.
|
||||||
$email_prompt = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
$new_email = null;
|
||||||
$email = nonempty(
|
if ($is_new) {
|
||||||
phutil_console_prompt("Enter user email address{$email_prompt}:"),
|
do {
|
||||||
$user_email);
|
$email = phutil_console_prompt("Enter user email address:");
|
||||||
$duplicate = id(new PhabricatorUser())->loadOneWhere(
|
$duplicate = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
'email = %s',
|
'address = %s',
|
||||||
$email);
|
$email);
|
||||||
if ($duplicate && $duplicate->getID() != $user->getID()) {
|
if ($duplicate) {
|
||||||
$duplicate_username = $duplicate->getUsername();
|
echo "ERROR: There is already a user with that email address. ".
|
||||||
echo "ERROR: There is already a user with that email address ".
|
"Each user must have a unique email address.\n";
|
||||||
"({$duplicate_username}). Each user must have a unique email ".
|
|
||||||
"address.\n";
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while (true);
|
} while (true);
|
||||||
$user->setEmail($email);
|
|
||||||
|
$new_email = $email;
|
||||||
|
}
|
||||||
|
|
||||||
$changed_pass = false;
|
$changed_pass = false;
|
||||||
// This disables local echo, so the user's password is not shown as they type
|
// This disables local echo, so the user's password is not shown as they type
|
||||||
|
@ -126,7 +128,9 @@ $tpl = "%12s %-30s %-30s\n";
|
||||||
printf($tpl, null, 'OLD VALUE', 'NEW VALUE');
|
printf($tpl, null, 'OLD VALUE', 'NEW VALUE');
|
||||||
printf($tpl, 'Username', $original->getUsername(), $user->getUsername());
|
printf($tpl, 'Username', $original->getUsername(), $user->getUsername());
|
||||||
printf($tpl, 'Real Name', $original->getRealName(), $user->getRealName());
|
printf($tpl, 'Real Name', $original->getRealName(), $user->getRealName());
|
||||||
printf($tpl, 'Email', $original->getEmail(), $user->getEmail());
|
if ($new_email) {
|
||||||
|
printf($tpl, 'Email', '', $new_email);
|
||||||
|
}
|
||||||
printf($tpl, 'Password', null,
|
printf($tpl, 'Password', null,
|
||||||
($changed_pass !== false)
|
($changed_pass !== false)
|
||||||
? 'Updated'
|
? 'Updated'
|
||||||
|
@ -153,4 +157,13 @@ if ($changed_pass !== false) {
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($new_email) {
|
||||||
|
id(new PhabricatorUserEmail())
|
||||||
|
->setUserPHID($user->getPHID())
|
||||||
|
->setAddress($new_email)
|
||||||
|
->setIsVerified(1)
|
||||||
|
->setIsPrimary(1)
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
|
||||||
echo "Saved changes.\n";
|
echo "Saved changes.\n";
|
||||||
|
|
|
@ -50,20 +50,26 @@ if ($existing_user) {
|
||||||
"There is already a user with the username '{$username}'!");
|
"There is already a user with the username '{$username}'!");
|
||||||
}
|
}
|
||||||
|
|
||||||
$existing_user = id(new PhabricatorUser())->loadOneWhere(
|
$existing_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
'email = %s',
|
'address = %s',
|
||||||
$email);
|
$email);
|
||||||
if ($existing_user) {
|
if ($existing_email) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"There is already a user with the email '{$email}'!");
|
"There is already a user with the email '{$email}'!");
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = new PhabricatorUser();
|
$user = new PhabricatorUser();
|
||||||
$user->setUsername($username);
|
$user->setUsername($username);
|
||||||
$user->setEmail($email);
|
|
||||||
$user->setRealname($realname);
|
$user->setRealname($realname);
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
|
$email_object = id(new PhabricatorUserEmail())
|
||||||
|
->setUserPHID($user->getPHID())
|
||||||
|
->setAddress($email)
|
||||||
|
->setIsVerified(1)
|
||||||
|
->setIsPrimary(1)
|
||||||
|
->save();
|
||||||
|
|
||||||
$user->sendWelcomeEmail($admin);
|
$user->sendWelcomeEmail($admin);
|
||||||
|
|
||||||
echo "Created user '{$username}' (realname='{$realname}', email='{$email}').\n";
|
echo "Created user '{$username}' (realname='{$realname}', email='{$email}').\n";
|
||||||
|
|
|
@ -595,6 +595,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/edge',
|
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/edge',
|
||||||
'PhabricatorEmailLoginController' => 'applications/auth/controller/email',
|
'PhabricatorEmailLoginController' => 'applications/auth/controller/email',
|
||||||
'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken',
|
'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken',
|
||||||
|
'PhabricatorEmailVerificationController' => 'applications/people/controller/emailverification',
|
||||||
'PhabricatorEnv' => 'infrastructure/env',
|
'PhabricatorEnv' => 'infrastructure/env',
|
||||||
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__',
|
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__',
|
||||||
'PhabricatorErrorExample' => 'applications/uiexample/examples/error',
|
'PhabricatorErrorExample' => 'applications/uiexample/examples/error',
|
||||||
|
@ -946,6 +947,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
|
'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
|
||||||
'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
|
'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
|
||||||
'PhabricatorUserDAO' => 'applications/people/storage/base',
|
'PhabricatorUserDAO' => 'applications/people/storage/base',
|
||||||
|
'PhabricatorUserEmail' => 'applications/people/storage/email',
|
||||||
'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/emailpref',
|
'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/emailpref',
|
||||||
'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email',
|
'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email',
|
||||||
'PhabricatorUserLog' => 'applications/people/storage/log',
|
'PhabricatorUserLog' => 'applications/people/storage/log',
|
||||||
|
@ -1534,6 +1536,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
|
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
|
||||||
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
|
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
|
||||||
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
|
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
|
||||||
|
'PhabricatorEmailVerificationController' => 'PhabricatorPeopleController',
|
||||||
'PhabricatorEnvTestCase' => 'PhabricatorTestCase',
|
'PhabricatorEnvTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorErrorExample' => 'PhabricatorUIExample',
|
'PhabricatorErrorExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorEvent' => 'PhutilEvent',
|
'PhabricatorEvent' => 'PhutilEvent',
|
||||||
|
@ -1819,6 +1822,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||||
'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||||
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
|
||||||
|
'PhabricatorUserEmail' => 'PhabricatorUserDAO',
|
||||||
'PhabricatorUserEmailPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
'PhabricatorUserEmailPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||||
'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||||
'PhabricatorUserLog' => 'PhabricatorUserDAO',
|
'PhabricatorUserLog' => 'PhabricatorUserDAO',
|
||||||
|
|
|
@ -433,6 +433,9 @@ class AphrontDefaultApplicationConfiguration
|
||||||
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
|
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'/emailverify/(?P<code>[^/]+)/' =>
|
||||||
|
'PhabricatorEmailVerificationController',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AphrontRedirectResponse extends AphrontResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getURI() {
|
public function getURI() {
|
||||||
return $this->uri;
|
return (string)$this->uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHeaders() {
|
public function getHeaders() {
|
||||||
|
|
|
@ -57,17 +57,24 @@ final class PhabricatorEmailLoginController
|
||||||
// it expensive to fish for valid email addresses while giving the user
|
// it expensive to fish for valid email addresses while giving the user
|
||||||
// a better error if they goof their email.
|
// a better error if they goof their email.
|
||||||
|
|
||||||
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
'email = %s',
|
'address = %s',
|
||||||
$email);
|
$email);
|
||||||
|
|
||||||
|
$target_user = null;
|
||||||
|
if ($target_email) {
|
||||||
|
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
||||||
|
'phid = %s',
|
||||||
|
$target_email->getUserPHID());
|
||||||
|
}
|
||||||
|
|
||||||
if (!$target_user) {
|
if (!$target_user) {
|
||||||
$errors[] = "There is no account associated with that email address.";
|
$errors[] = "There is no account associated with that email address.";
|
||||||
$e_email = "Invalid";
|
$e_email = "Invalid";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
$uri = $target_user->getEmailLoginURI();
|
$uri = $target_user->getEmailLoginURI($target_email);
|
||||||
if ($is_serious) {
|
if ($is_serious) {
|
||||||
$body = <<<EOBODY
|
$body = <<<EOBODY
|
||||||
You can use this link to reset your Phabricator password:
|
You can use this link to reset your Phabricator password:
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'aphront/response/400');
|
phutil_require_module('phabricator', 'aphront/response/400');
|
||||||
phutil_require_module('phabricator', 'applications/auth/controller/base');
|
phutil_require_module('phabricator', 'applications/auth/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
|
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'view/form/base');
|
phutil_require_module('phabricator', 'view/form/base');
|
||||||
|
|
|
@ -55,11 +55,31 @@ final class PhabricatorEmailTokenController
|
||||||
$token = $this->token;
|
$token = $this->token;
|
||||||
$email = $request->getStr('email');
|
$email = $request->getStr('email');
|
||||||
|
|
||||||
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
// NOTE: We need to bind verification to **addresses**, not **users**,
|
||||||
'email = %s',
|
// because we verify addresses when they're used to login this way, and if
|
||||||
|
// we have a user-based verification you can:
|
||||||
|
//
|
||||||
|
// - Add some address you do not own;
|
||||||
|
// - request a password reset;
|
||||||
|
// - change the URI in the email to the address you don't own;
|
||||||
|
// - login via the email link; and
|
||||||
|
// - get a "verified" address you don't control.
|
||||||
|
|
||||||
|
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'address = %s',
|
||||||
$email);
|
$email);
|
||||||
|
|
||||||
if (!$target_user || !$target_user->validateEmailToken($token)) {
|
$target_user = null;
|
||||||
|
if ($target_email) {
|
||||||
|
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
||||||
|
'phid = %s',
|
||||||
|
$target_email->getUserPHID());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$target_email ||
|
||||||
|
!$target_user ||
|
||||||
|
!$target_user->validateEmailToken($target_email, $token)) {
|
||||||
|
|
||||||
$view = new AphrontRequestFailureView();
|
$view = new AphrontRequestFailureView();
|
||||||
$view->setHeader('Unable to Login');
|
$view->setHeader('Unable to Login');
|
||||||
$view->appendChild(
|
$view->appendChild(
|
||||||
|
@ -71,19 +91,32 @@ final class PhabricatorEmailTokenController
|
||||||
'<div class="aphront-failure-continue">'.
|
'<div class="aphront-failure-continue">'.
|
||||||
'<a class="button" href="/login/email/">Send Another Email</a>'.
|
'<a class="button" href="/login/email/">Send Another Email</a>'.
|
||||||
'</div>');
|
'</div>');
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
return $this->buildStandardPageResponse(
|
||||||
$view,
|
$view,
|
||||||
array(
|
array(
|
||||||
'title' => 'Email Sent',
|
'title' => 'Login Failure',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify email so that clicking the link in the "Welcome" email is good
|
||||||
|
// enough, without requiring users to go through a second round of email
|
||||||
|
// verification.
|
||||||
|
|
||||||
|
$target_email->setIsVerified(1);
|
||||||
|
$target_email->save();
|
||||||
|
|
||||||
$session_key = $target_user->establishSession('web');
|
$session_key = $target_user->establishSession('web');
|
||||||
$request->setCookie('phusr', $target_user->getUsername());
|
$request->setCookie('phusr', $target_user->getUsername());
|
||||||
$request->setCookie('phsid', $session_key);
|
$request->setCookie('phsid', $session_key);
|
||||||
|
|
||||||
if (PhabricatorEnv::getEnvConfig('account.editable')) {
|
if (PhabricatorEnv::getEnvConfig('account.editable')) {
|
||||||
$next = '/settings/page/password/?token='.$token;
|
$next = (string)id(new PhutilURI('/settings/page/password/'))
|
||||||
|
->setQueryParams(
|
||||||
|
array(
|
||||||
|
'token' => $token,
|
||||||
|
'email' => $email,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
$next = '/';
|
$next = '/';
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'aphront/response/400');
|
phutil_require_module('phabricator', 'aphront/response/400');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/auth/controller/base');
|
phutil_require_module('phabricator', 'applications/auth/controller/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'view/page/failure');
|
phutil_require_module('phabricator', 'view/page/failure');
|
||||||
|
|
|
@ -113,9 +113,7 @@ final class PhabricatorLoginController
|
||||||
$username_or_email);
|
$username_or_email);
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
$user = PhabricatorUser::loadOneWithEmailAddress($username_or_email);
|
||||||
'email = %s',
|
|
||||||
$username_or_email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
|
|
|
@ -176,8 +176,8 @@ final class PhabricatorOAuthLoginController
|
||||||
|
|
||||||
$oauth_email = $provider->retrieveUserEmail();
|
$oauth_email = $provider->retrieveUserEmail();
|
||||||
if ($oauth_email) {
|
if ($oauth_email) {
|
||||||
$known_email = id(new PhabricatorUser())
|
$known_email = id(new PhabricatorUserEmail())
|
||||||
->loadOneWhere('email = %s', $oauth_email);
|
->loadOneWhere('address = %s', $oauth_email);
|
||||||
if ($known_email) {
|
if ($known_email) {
|
||||||
$dialog = new AphrontDialogView();
|
$dialog = new AphrontDialogView();
|
||||||
$dialog->setUser($current_user);
|
$dialog->setUser($current_user);
|
||||||
|
|
|
@ -13,6 +13,7 @@ phutil_require_module('phabricator', 'aphront/writeguard');
|
||||||
phutil_require_module('phabricator', 'applications/auth/controller/base');
|
phutil_require_module('phabricator', 'applications/auth/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/auth/oauth/provider/base');
|
phutil_require_module('phabricator', 'applications/auth/oauth/provider/base');
|
||||||
phutil_require_module('phabricator', 'applications/auth/view/oauthfailure');
|
phutil_require_module('phabricator', 'applications/auth/view/oauthfailure');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo');
|
phutil_require_module('phabricator', 'applications/people/storage/useroauthinfo');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
|
|
@ -33,7 +33,8 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
|
|
||||||
$user->setUsername($provider->retrieveUserAccountName());
|
$user->setUsername($provider->retrieveUserAccountName());
|
||||||
$user->setRealName($provider->retrieveUserRealName());
|
$user->setRealName($provider->retrieveUserRealName());
|
||||||
$user->setEmail($provider->retrieveUserEmail());
|
|
||||||
|
$new_email = $provider->retrieveUserEmail();
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
|
|
||||||
|
@ -49,9 +50,9 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
$e_username = null;
|
$e_username = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->getEmail() === null) {
|
if (!$new_email) {
|
||||||
$user->setEmail($request->getStr('email'));
|
$new_email = trim($request->getStr('email'));
|
||||||
if (!strlen($user->getEmail())) {
|
if (!$new_email) {
|
||||||
$e_email = 'Required';
|
$e_email = 'Required';
|
||||||
$errors[] = 'Email is required.';
|
$errors[] = 'Email is required.';
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,12 +85,29 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
try {
|
try {
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
|
// NOTE: We don't verify OAuth email addresses by default because
|
||||||
|
// OAuth providers might associate email addresses with accounts that
|
||||||
|
// haven't actually verified they own them. We could selectively
|
||||||
|
// auto-verify some providers that we trust here, but the stakes for
|
||||||
|
// verifying an email address are high because having a corporate
|
||||||
|
// address at a company is sometimes the key to the castle.
|
||||||
|
|
||||||
|
$new_email = id(new PhabricatorUserEmail())
|
||||||
|
->setUserPHID($user->getPHID())
|
||||||
|
->setAddress($new_email)
|
||||||
|
->setIsPrimary(1)
|
||||||
|
->setIsVerified(0)
|
||||||
|
->save();
|
||||||
|
|
||||||
$oauth_info->setUserID($user->getID());
|
$oauth_info->setUserID($user->getID());
|
||||||
$oauth_info->save();
|
$oauth_info->save();
|
||||||
|
|
||||||
$session_key = $user->establishSession('web');
|
$session_key = $user->establishSession('web');
|
||||||
$request->setCookie('phusr', $user->getUsername());
|
$request->setCookie('phusr', $user->getUsername());
|
||||||
$request->setCookie('phsid', $session_key);
|
$request->setCookie('phsid', $session_key);
|
||||||
|
|
||||||
|
$new_email->sendVerificationEmail($user);
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())->setURI('/');
|
return id(new AphrontRedirectResponse())->setURI('/');
|
||||||
} catch (AphrontQueryDuplicateKeyException $exception) {
|
} catch (AphrontQueryDuplicateKeyException $exception) {
|
||||||
|
|
||||||
|
@ -97,9 +115,9 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
'userName = %s',
|
'userName = %s',
|
||||||
$user->getUserName());
|
$user->getUserName());
|
||||||
|
|
||||||
$same_email = id(new PhabricatorUser())->loadOneWhere(
|
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
'email = %s',
|
'address = %s',
|
||||||
$user->getEmail());
|
$new_email);
|
||||||
|
|
||||||
if ($same_username) {
|
if ($same_username) {
|
||||||
$e_username = 'Duplicate';
|
$e_username = 'Duplicate';
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/auth/controller/oauthregistration/base');
|
phutil_require_module('phabricator', 'applications/auth/controller/oauthregistration/base');
|
||||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'view/form/base');
|
phutil_require_module('phabricator', 'view/form/base');
|
||||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||||
|
|
|
@ -26,7 +26,6 @@ abstract class ConduitAPI_user_Method extends ConduitAPIMethod {
|
||||||
'phid' => $user->getPHID(),
|
'phid' => $user->getPHID(),
|
||||||
'userName' => $user->getUserName(),
|
'userName' => $user->getUserName(),
|
||||||
'realName' => $user->getRealName(),
|
'realName' => $user->getRealName(),
|
||||||
'email' => $user->getEmail(),
|
|
||||||
'image' => $user->loadProfileImageURI(),
|
'image' => $user->loadProfileImageURI(),
|
||||||
'uri' => PhabricatorEnv::getURI('/p/'.$user->getUsername().'/'),
|
'uri' => PhabricatorEnv::getURI('/p/'.$user->getUsername().'/'),
|
||||||
);
|
);
|
||||||
|
|
|
@ -672,8 +672,7 @@ abstract class DifferentialFieldSpecification {
|
||||||
$object_map = array();
|
$object_map = array();
|
||||||
|
|
||||||
$users = id(new PhabricatorUser())->loadAllWhere(
|
$users = id(new PhabricatorUser())->loadAllWhere(
|
||||||
'(username IN (%Ls)) OR (email IN (%Ls))',
|
'(username IN (%Ls))',
|
||||||
$value,
|
|
||||||
$value);
|
$value);
|
||||||
|
|
||||||
$user_map = mpull($users, 'getPHID', 'getUsername');
|
$user_map = mpull($users, 'getPHID', 'getUsername');
|
||||||
|
|
|
@ -108,6 +108,11 @@ final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addRawTos(array $raw_email) {
|
||||||
|
$this->setParam('raw-to', $raw_email);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function addCCs(array $phids) {
|
public function addCCs(array $phids) {
|
||||||
$phids = array_unique($phids);
|
$phids = array_unique($phids);
|
||||||
$this->setParam('cc', $phids);
|
$this->setParam('cc', $phids);
|
||||||
|
@ -367,9 +372,12 @@ final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO {
|
||||||
$handles,
|
$handles,
|
||||||
$exclude);
|
$exclude);
|
||||||
if ($emails) {
|
if ($emails) {
|
||||||
$add_to = $emails;
|
$add_to = array_merge($add_to, $emails);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'raw-to':
|
||||||
|
$add_to = array_merge($add_to, $value);
|
||||||
|
break;
|
||||||
case 'cc':
|
case 'cc':
|
||||||
$emails = $this->getDeliverableEmailsFromHandles(
|
$emails = $this->getDeliverableEmailsFromHandles(
|
||||||
$value,
|
$value,
|
||||||
|
|
|
@ -274,9 +274,7 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
|
||||||
$from = idx($this->headers, 'from');
|
$from = idx($this->headers, 'from');
|
||||||
$from = $this->getRawEmailAddress($from);
|
$from = $this->getRawEmailAddress($from);
|
||||||
|
|
||||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
$user = PhabricatorUser::loadOneWithEmailAddress($from);
|
||||||
'email = %s',
|
|
||||||
$from);
|
|
||||||
|
|
||||||
// If Phabricator is configured to allow "Reply-To" authentication, try
|
// If Phabricator is configured to allow "Reply-To" authentication, try
|
||||||
// the "Reply-To" address if we failed to match the "From" address.
|
// the "Reply-To" address if we failed to match the "From" address.
|
||||||
|
@ -287,9 +285,7 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
|
||||||
$reply_to = idx($this->headers, 'reply-to');
|
$reply_to = idx($this->headers, 'reply-to');
|
||||||
$reply_to = $this->getRawEmailAddress($reply_to);
|
$reply_to = $this->getRawEmailAddress($reply_to);
|
||||||
if ($reply_to) {
|
if ($reply_to) {
|
||||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
$user = PhabricatorUser::loadOneWithEmailAddress($reply_to);
|
||||||
'email = %s',
|
|
||||||
$reply_to);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,13 +123,20 @@ final class PhabricatorPeopleEditController
|
||||||
|
|
||||||
$welcome_checked = true;
|
$welcome_checked = true;
|
||||||
|
|
||||||
|
$new_email = null;
|
||||||
|
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
$welcome_checked = $request->getInt('welcome');
|
$welcome_checked = $request->getInt('welcome');
|
||||||
|
|
||||||
if (!$user->getID()) {
|
if (!$user->getID()) {
|
||||||
$user->setUsername($request->getStr('username'));
|
$user->setUsername($request->getStr('username'));
|
||||||
$user->setEmail($request->getStr('email'));
|
|
||||||
|
$new_email = $request->getStr('email');
|
||||||
|
if (!strlen($new_email)) {
|
||||||
|
$errors[] = 'Email is required.';
|
||||||
|
$e_email = 'Required';
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->getStr('role') == 'agent') {
|
if ($request->getStr('role') == 'agent') {
|
||||||
$user->setIsSystemAgent(true);
|
$user->setIsSystemAgent(true);
|
||||||
|
@ -154,13 +161,6 @@ final class PhabricatorPeopleEditController
|
||||||
$e_realname = null;
|
$e_realname = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strlen($user->getEmail())) {
|
|
||||||
$errors[] = 'Email is required.';
|
|
||||||
$e_email = 'Required';
|
|
||||||
} else {
|
|
||||||
$e_email = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
try {
|
try {
|
||||||
$is_new = !$user->getID();
|
$is_new = !$user->getID();
|
||||||
|
@ -168,6 +168,14 @@ final class PhabricatorPeopleEditController
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
if ($is_new) {
|
if ($is_new) {
|
||||||
|
|
||||||
|
$email = id(new PhabricatorUserEmail())
|
||||||
|
->setUserPHID($user->getPHID())
|
||||||
|
->setAddress($new_email)
|
||||||
|
->setIsPrimary(1)
|
||||||
|
->setIsVerified(0)
|
||||||
|
->save();
|
||||||
|
|
||||||
$log = PhabricatorUserLog::newLog(
|
$log = PhabricatorUserLog::newLog(
|
||||||
$admin,
|
$admin,
|
||||||
$user,
|
$user,
|
||||||
|
@ -187,8 +195,8 @@ final class PhabricatorPeopleEditController
|
||||||
|
|
||||||
$same_username = id(new PhabricatorUser())
|
$same_username = id(new PhabricatorUser())
|
||||||
->loadOneWhere('username = %s', $user->getUsername());
|
->loadOneWhere('username = %s', $user->getUsername());
|
||||||
$same_email = id(new PhabricatorUser())
|
$same_email = id(new PhabricatorUserEmail())
|
||||||
->loadOneWhere('email = %s', $user->getEmail());
|
->loadOneWhere('address = %s', $new_email);
|
||||||
|
|
||||||
if ($same_username) {
|
if ($same_username) {
|
||||||
$e_username = 'Duplicate';
|
$e_username = 'Duplicate';
|
||||||
|
@ -236,15 +244,19 @@ final class PhabricatorPeopleEditController
|
||||||
->setLabel('Real Name')
|
->setLabel('Real Name')
|
||||||
->setName('realname')
|
->setName('realname')
|
||||||
->setValue($user->getRealName())
|
->setValue($user->getRealName())
|
||||||
->setError($e_realname))
|
->setError($e_realname));
|
||||||
->appendChild(
|
|
||||||
|
if (!$user->getID()) {
|
||||||
|
$form->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('Email')
|
->setLabel('Email')
|
||||||
->setName('email')
|
->setName('email')
|
||||||
->setDisabled($is_immutable)
|
->setDisabled($is_immutable)
|
||||||
->setValue($user->getEmail())
|
->setValue($new_email)
|
||||||
->setError($e_email))
|
->setError($e_email));
|
||||||
->appendChild($this->getRoleInstructions());
|
}
|
||||||
|
|
||||||
|
$form->appendChild($this->getRoleInstructions());
|
||||||
|
|
||||||
if (!$user->getID()) {
|
if (!$user->getID()) {
|
||||||
$form
|
$form
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
phutil_require_module('phabricator', 'aphront/response/404');
|
phutil_require_module('phabricator', 'aphront/response/404');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/people/controller/base');
|
phutil_require_module('phabricator', 'applications/people/controller/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/log');
|
phutil_require_module('phabricator', 'applications/people/storage/log');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorEmailVerificationController
|
||||||
|
extends PhabricatorPeopleController {
|
||||||
|
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
public function willProcessRequest(array $data) {
|
||||||
|
$this->code = $data['code'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'userPHID = %s AND verificationCode = %s',
|
||||||
|
$user->getPHID(),
|
||||||
|
$this->code);
|
||||||
|
|
||||||
|
$settings_link = phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => '/settings/page/email/',
|
||||||
|
),
|
||||||
|
'Return to Email Settings');
|
||||||
|
$settings_link = '<br /><p><strong>'.$settings_link.'</strong></p>';
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
$content = id(new AphrontErrorView())
|
||||||
|
->setTitle('Unable To Verify')
|
||||||
|
->appendChild(
|
||||||
|
'<p>The verification code is incorrect, the email address has '.
|
||||||
|
'been removed, or the email address is owned by another user. Make '.
|
||||||
|
'sure you followed the link in the email correctly.</p>');
|
||||||
|
} else if ($email->getIsVerified()) {
|
||||||
|
$content = id(new AphrontErrorView())
|
||||||
|
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||||
|
->setTitle('Address Already Verified')
|
||||||
|
->appendChild(
|
||||||
|
'<p>This email address has already been verified.</p>'.
|
||||||
|
$settings_link);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$guard = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
$email->setIsVerified(1);
|
||||||
|
$email->save();
|
||||||
|
unset($guard);
|
||||||
|
|
||||||
|
$content = id(new AphrontErrorView())
|
||||||
|
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
|
||||||
|
->setTitle('Address Verified')
|
||||||
|
->appendChild(
|
||||||
|
'<p>This email address has now been verified. Thanks!</p>'.
|
||||||
|
$settings_link);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$content,
|
||||||
|
array(
|
||||||
|
'title' => 'Verify Email',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'aphront/writeguard');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/controller/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
|
phutil_require_module('phabricator', 'view/form/error');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'markup');
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhabricatorEmailVerificationController.php');
|
|
@ -94,7 +94,7 @@ final class PhabricatorUserSettingsController
|
||||||
->addFilter('profile', 'Profile')
|
->addFilter('profile', 'Profile')
|
||||||
->addSpacer()
|
->addSpacer()
|
||||||
->addLabel('Email')
|
->addLabel('Email')
|
||||||
->addFilter('email', 'Email Address')
|
->addFilter('email', 'Email Addresses')
|
||||||
->addFilter('emailpref', 'Email Preferences')
|
->addFilter('emailpref', 'Email Preferences')
|
||||||
->addSpacer()
|
->addSpacer()
|
||||||
->addLabel('Authentication');
|
->addLabel('Authentication');
|
||||||
|
|
|
@ -25,72 +25,317 @@ final class PhabricatorUserEmailSettingsPanelController
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
$editable = $this->getAccountEditable();
|
$editable = $this->getAccountEditable();
|
||||||
|
|
||||||
$e_email = true;
|
$uri = $request->getRequestURI();
|
||||||
$errors = array();
|
$uri->setQueryParams(array());
|
||||||
if ($request->isFormPost()) {
|
|
||||||
if (!$editable) {
|
if ($editable) {
|
||||||
return new Aphront400Response();
|
$new = $request->getStr('new');
|
||||||
|
if ($new) {
|
||||||
|
return $this->returnNewAddressResponse($uri, $new);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->setEmail($request->getStr('email'));
|
$delete = $request->getInt('delete');
|
||||||
|
if ($delete) {
|
||||||
if (!strlen($user->getEmail())) {
|
return $this->returnDeleteAddressResponse($uri, $delete);
|
||||||
$errors[] = 'You must enter an e-mail address.';
|
|
||||||
$e_email = 'Required';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
|
||||||
$user->save();
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI('/settings/page/email/?saved=true');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$notice = null;
|
$verify = $request->getInt('verify');
|
||||||
if (!$errors) {
|
if ($verify) {
|
||||||
if ($request->getStr('saved')) {
|
return $this->returnVerifyAddressResponse($uri, $verify);
|
||||||
$notice = new AphrontErrorView();
|
|
||||||
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
|
|
||||||
$notice->setTitle('Changes Saved');
|
|
||||||
$notice->appendChild('<p>Your changes have been saved.</p>');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$primary = $request->getInt('primary');
|
||||||
|
if ($primary) {
|
||||||
|
return $this->returnPrimaryAddressResponse($uri, $primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
||||||
|
'userPHID = %s',
|
||||||
|
$user->getPHID());
|
||||||
|
|
||||||
|
$rowc = array();
|
||||||
|
$rows = array();
|
||||||
|
foreach ($emails as $email) {
|
||||||
|
|
||||||
|
if ($email->getIsPrimary()) {
|
||||||
|
$action = phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'button small disabled',
|
||||||
|
),
|
||||||
|
'Primary');
|
||||||
|
$remove = $action;
|
||||||
|
$rowc[] = 'highlighted';
|
||||||
} else {
|
} else {
|
||||||
$notice = new AphrontErrorView();
|
if ($email->getIsVerified()) {
|
||||||
$notice->setTitle('Form Errors');
|
$action = javelin_render_tag(
|
||||||
$notice->setErrors($errors);
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'button small grey',
|
||||||
|
'href' => $uri->alter('primary', $email->getID()),
|
||||||
|
'sigil' => 'workflow',
|
||||||
|
),
|
||||||
|
'Make Primary');
|
||||||
|
} else {
|
||||||
|
$action = javelin_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'button small grey',
|
||||||
|
'href' => $uri->alter('verify', $email->getID()),
|
||||||
|
'sigil' => 'workflow',
|
||||||
|
),
|
||||||
|
'Verify');
|
||||||
|
}
|
||||||
|
$remove = javelin_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'button small grey',
|
||||||
|
'href' => $uri->alter('delete', $email->getID()),
|
||||||
|
'sigil' => 'workflow'
|
||||||
|
),
|
||||||
|
'Remove');
|
||||||
|
$rowc[] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = new AphrontFormView();
|
$rows[] = array(
|
||||||
$form
|
phutil_escape_html($email->getAddress()),
|
||||||
|
$action,
|
||||||
|
$remove,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = new AphrontTableView($rows);
|
||||||
|
$table->setHeaders(
|
||||||
|
array(
|
||||||
|
'Email',
|
||||||
|
'Status',
|
||||||
|
'Remove',
|
||||||
|
));
|
||||||
|
$table->setColumnClasses(
|
||||||
|
array(
|
||||||
|
'wide',
|
||||||
|
'action',
|
||||||
|
'action',
|
||||||
|
));
|
||||||
|
$table->setRowClasses($rowc);
|
||||||
|
$table->setColumnVisibility(
|
||||||
|
array(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
$editable,
|
||||||
|
));
|
||||||
|
|
||||||
|
$view = new AphrontPanelView();
|
||||||
|
if ($editable) {
|
||||||
|
$view->addButton(
|
||||||
|
javelin_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $uri->alter('new', 'true'),
|
||||||
|
'class' => 'green button',
|
||||||
|
'sigil' => 'workflow',
|
||||||
|
),
|
||||||
|
'Add New Address'));
|
||||||
|
}
|
||||||
|
$view->setHeader('Email Addresses');
|
||||||
|
$view->appendChild($table);
|
||||||
|
|
||||||
|
return $view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function returnNewAddressResponse(PhutilURI $uri, $new) {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$e_email = true;
|
||||||
|
$email = trim($request->getStr('email'));
|
||||||
|
$errors = array();
|
||||||
|
if ($request->isDialogFormPost()) {
|
||||||
|
|
||||||
|
if ($new == 'verify') {
|
||||||
|
// The user clicked "Done" from the "an email has been sent" dialog.
|
||||||
|
return id(new AphrontReloadResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strlen($email)) {
|
||||||
|
$e_email = 'Required';
|
||||||
|
$errors[] = 'Email is required.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errors) {
|
||||||
|
$object = id(new PhabricatorUserEmail())
|
||||||
|
->setUserPHID($user->getPHID())
|
||||||
|
->setAddress($email)
|
||||||
|
->setIsVerified(0)
|
||||||
|
->setIsPrimary(0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$object->save();
|
||||||
|
|
||||||
|
$object->sendVerificationEmail($user);
|
||||||
|
|
||||||
|
$dialog = id(new AphrontDialogView())
|
||||||
->setUser($user)
|
->setUser($user)
|
||||||
|
->addHiddenInput('new', 'verify')
|
||||||
|
->setTitle('Verification Email Sent')
|
||||||
|
->appendChild(
|
||||||
|
'<p>A verification email has been sent. Click the link in the '.
|
||||||
|
'email to verify your address.</p>')
|
||||||
|
->setSubmitURI($uri)
|
||||||
|
->addSubmitButton('Done');
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||||
|
$email = 'Duplicate';
|
||||||
|
$errors[] = 'Another user already has this email.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors) {
|
||||||
|
$errors = id(new AphrontErrorView())
|
||||||
|
->setWidth(AphrontErrorView::WIDTH_DIALOG)
|
||||||
|
->setErrors($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormLayoutView())
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('Email')
|
->setLabel('Email')
|
||||||
->setName('email')
|
->setName('email')
|
||||||
->setDisabled(!$editable)
|
->setValue($request->getStr('email'))
|
||||||
->setCaption(
|
|
||||||
'Note: there is no email validation yet; double-check your '.
|
|
||||||
'typing.')
|
|
||||||
->setValue($user->getEmail())
|
|
||||||
->setError($e_email));
|
->setError($e_email));
|
||||||
|
|
||||||
if ($editable) {
|
$dialog = id(new AphrontDialogView())
|
||||||
$form
|
->setUser($user)
|
||||||
->appendChild(
|
->addHiddenInput('new', 'true')
|
||||||
id(new AphrontFormSubmitControl())
|
->setTitle('New Address')
|
||||||
->setValue('Save'));
|
->appendChild($errors)
|
||||||
|
->appendChild($form)
|
||||||
|
->addSubmitButton('Save')
|
||||||
|
->addCancelButton($uri);
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
$panel = new AphrontPanelView();
|
private function returnDeleteAddressResponse(PhutilURI $uri, $email_id) {
|
||||||
$panel->setHeader('Email Settings');
|
$request = $this->getRequest();
|
||||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
$user = $request->getUser();
|
||||||
$panel->appendChild($form);
|
|
||||||
|
|
||||||
return id(new AphrontNullView())
|
// NOTE: You can only delete your own email addresses, and you can not
|
||||||
->appendChild(
|
// delete your primary address.
|
||||||
array(
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
$notice,
|
'id = %d AND userPHID = %s AND isPrimary = 0',
|
||||||
$panel,
|
$email_id,
|
||||||
));
|
$user->getPHID());
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$email->delete();
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$address = $email->getAddress();
|
||||||
|
|
||||||
|
$dialog = id(new AphrontDialogView())
|
||||||
|
->setUser($user)
|
||||||
|
->addHiddenInput('delete', $email_id)
|
||||||
|
->setTitle("Really delete address '{$address}'?")
|
||||||
|
->appendChild(
|
||||||
|
'<p>Are you sure you want to delete this address? You will no '.
|
||||||
|
'longer be able to use it to login.</p>')
|
||||||
|
->addSubmitButton('Delete')
|
||||||
|
->addCancelButton($uri);
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function returnVerifyAddressResponse(PhutilURI $uri, $email_id) {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
// NOTE: You can only send more email for your unverified addresses.
|
||||||
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'id = %d AND userPHID = %s AND isVerified = 0',
|
||||||
|
$email_id,
|
||||||
|
$user->getPHID());
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$email->sendVerificationEmail($user);
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$address = $email->getAddress();
|
||||||
|
|
||||||
|
$dialog = id(new AphrontDialogView())
|
||||||
|
->setUser($user)
|
||||||
|
->addHiddenInput('verify', $email_id)
|
||||||
|
->setTitle("Send Another Verification Email?")
|
||||||
|
->appendChild(
|
||||||
|
'<p>Send another copy of the verification email to '.
|
||||||
|
phutil_escape_html($address).'?</p>')
|
||||||
|
->addSubmitButton('Send Email')
|
||||||
|
->addCancelButton($uri);
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function returnPrimaryAddressResponse(PhutilURI $uri, $email_id) {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
// NOTE: You can only make your own verified addresses primary.
|
||||||
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'id = %d AND userPHID = %s AND isVerified = 1 AND isPrimary = 0',
|
||||||
|
$email_id,
|
||||||
|
$user->getPHID());
|
||||||
|
|
||||||
|
if (!$email) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
|
||||||
|
// TODO: Transactions!
|
||||||
|
|
||||||
|
$email->setIsPrimary(1);
|
||||||
|
|
||||||
|
$old_primary = $user->loadPrimaryEmail();
|
||||||
|
if ($old_primary) {
|
||||||
|
$old_primary->setIsPrimary(0);
|
||||||
|
$old_primary->save();
|
||||||
|
}
|
||||||
|
$email->save();
|
||||||
|
|
||||||
|
if ($old_primary) {
|
||||||
|
$old_primary->sendOldPrimaryEmail($user, $email);
|
||||||
|
}
|
||||||
|
$email->sendNewPrimaryEmail($user);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$address = $email->getAddress();
|
||||||
|
|
||||||
|
$dialog = id(new AphrontDialogView())
|
||||||
|
->setUser($user)
|
||||||
|
->addHiddenInput('primary', $email_id)
|
||||||
|
->setTitle("Change primary email address?")
|
||||||
|
->appendChild(
|
||||||
|
'<p>If you change your primary address, Phabricator will send all '.
|
||||||
|
'email to '.phutil_escape_html($address).'.</p>')
|
||||||
|
->addSubmitButton('Change Primary Address')
|
||||||
|
->addCancelButton($uri);
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,21 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
phutil_require_module('phabricator', 'aphront/response/400');
|
phutil_require_module('phabricator', 'aphront/response/404');
|
||||||
|
phutil_require_module('phabricator', 'aphront/response/dialog');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
|
phutil_require_module('phabricator', 'aphront/response/reload');
|
||||||
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base');
|
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base');
|
||||||
phutil_require_module('phabricator', 'view/form/base');
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||||
|
phutil_require_module('phabricator', 'view/control/table');
|
||||||
|
phutil_require_module('phabricator', 'view/dialog');
|
||||||
phutil_require_module('phabricator', 'view/form/control/text');
|
phutil_require_module('phabricator', 'view/form/control/text');
|
||||||
phutil_require_module('phabricator', 'view/form/error');
|
phutil_require_module('phabricator', 'view/form/error');
|
||||||
|
phutil_require_module('phabricator', 'view/form/layout');
|
||||||
phutil_require_module('phabricator', 'view/layout/panel');
|
phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
phutil_require_module('phabricator', 'view/null');
|
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'markup');
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,10 +40,16 @@ final class PhabricatorUserPasswordSettingsPanelController
|
||||||
// the workflow from a password reset email.
|
// the workflow from a password reset email.
|
||||||
|
|
||||||
$token = $request->getStr('token');
|
$token = $request->getStr('token');
|
||||||
if ($token) {
|
|
||||||
$valid_token = $user->validateEmailToken($token);
|
|
||||||
} else {
|
|
||||||
$valid_token = false;
|
$valid_token = false;
|
||||||
|
if ($token) {
|
||||||
|
$email_address = $request->getStr('email');
|
||||||
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'address = %s',
|
||||||
|
$email_address);
|
||||||
|
if ($email) {
|
||||||
|
$valid_token = $user->validateEmailToken($email, $token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$e_old = true;
|
$e_old = true;
|
||||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/response/400');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'aphront/writeguard');
|
phutil_require_module('phabricator', 'aphront/writeguard');
|
||||||
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base');
|
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'view/form/base');
|
phutil_require_module('phabricator', 'view/form/base');
|
||||||
phutil_require_module('phabricator', 'view/form/control/password');
|
phutil_require_module('phabricator', 'view/form/control/password');
|
||||||
|
|
160
src/applications/people/storage/email/PhabricatorUserEmail.php
Normal file
160
src/applications/people/storage/email/PhabricatorUserEmail.php
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task email Email About Email
|
||||||
|
*/
|
||||||
|
final class PhabricatorUserEmail extends PhabricatorUserDAO {
|
||||||
|
|
||||||
|
protected $userPHID;
|
||||||
|
protected $address;
|
||||||
|
protected $isVerified;
|
||||||
|
protected $isPrimary;
|
||||||
|
protected $verificationCode;
|
||||||
|
|
||||||
|
public function getVerificationURI() {
|
||||||
|
return '/emailverify/'.$this->getVerificationCode().'/';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save() {
|
||||||
|
if (!$this->verificationCode) {
|
||||||
|
$this->setVerificationCode(Filesystem::readRandomCharacters(24));
|
||||||
|
}
|
||||||
|
return parent::save();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Email About Email )-------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a verification email from $user to this address.
|
||||||
|
*
|
||||||
|
* @param PhabricatorUser The user sending the verification.
|
||||||
|
* @return this
|
||||||
|
* @task email
|
||||||
|
*/
|
||||||
|
public function sendVerificationEmail(PhabricatorUser $user) {
|
||||||
|
$username = $user->getUsername();
|
||||||
|
|
||||||
|
$address = $this->getAddress();
|
||||||
|
$link = PhabricatorEnv::getProductionURI($this->getVerificationURI());
|
||||||
|
|
||||||
|
|
||||||
|
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
||||||
|
|
||||||
|
$signature = null;
|
||||||
|
if (!$is_serious) {
|
||||||
|
$signature = <<<EOSIGNATURE
|
||||||
|
Get Well Soon,
|
||||||
|
Phabricator
|
||||||
|
EOSIGNATURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = <<<EOBODY
|
||||||
|
Hi {$username},
|
||||||
|
|
||||||
|
Please verify that you own this email address ({$address}) by clicking this
|
||||||
|
link:
|
||||||
|
|
||||||
|
{$link}
|
||||||
|
|
||||||
|
{$signature}
|
||||||
|
EOBODY;
|
||||||
|
|
||||||
|
id(new PhabricatorMetaMTAMail())
|
||||||
|
->addRawTos(array($address))
|
||||||
|
->setSubject('[Phabricator] Email Verification')
|
||||||
|
->setBody($body)
|
||||||
|
->setFrom($user->getPHID())
|
||||||
|
->setRelatedPHID($user->getPHID())
|
||||||
|
->saveAndSend();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification email from $user to this address, informing the
|
||||||
|
* recipient that this is no longer their account's primary address.
|
||||||
|
*
|
||||||
|
* @param PhabricatorUser The user sending the notification.
|
||||||
|
* @param PhabricatorUserEmail New primary email address.
|
||||||
|
* @return this
|
||||||
|
* @task email
|
||||||
|
*/
|
||||||
|
public function sendOldPrimaryEmail(
|
||||||
|
PhabricatorUser $user,
|
||||||
|
PhabricatorUserEmail $new) {
|
||||||
|
$username = $user->getUsername();
|
||||||
|
|
||||||
|
$old_address = $this->getAddress();
|
||||||
|
$new_address = $new->getAddress();
|
||||||
|
|
||||||
|
$body = <<<EOBODY
|
||||||
|
Hi {$username},
|
||||||
|
|
||||||
|
This email address ({$old_address}) is no longer your primary email address.
|
||||||
|
Going forward, Phabricator will send all email to your new primary email
|
||||||
|
address ({$new_address}).
|
||||||
|
|
||||||
|
EOBODY;
|
||||||
|
|
||||||
|
id(new PhabricatorMetaMTAMail())
|
||||||
|
->addRawTos(array($old_address))
|
||||||
|
->setSubject('[Phabricator] Primary Address Changed')
|
||||||
|
->setBody($body)
|
||||||
|
->setFrom($user->getPHID())
|
||||||
|
->setRelatedPHID($user->getPHID())
|
||||||
|
->saveAndSend();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification email from $user to this address, informing the
|
||||||
|
* recipient that this is now their account's new primary email address.
|
||||||
|
*
|
||||||
|
* @param PhabricatorUser The user sending the verification.
|
||||||
|
* @return this
|
||||||
|
* @task email
|
||||||
|
*/
|
||||||
|
public function sendNewPrimaryEmail(PhabricatorUser $user) {
|
||||||
|
$username = $user->getUsername();
|
||||||
|
|
||||||
|
$new_address = $this->getAddress();
|
||||||
|
|
||||||
|
$body = <<<EOBODY
|
||||||
|
Hi {$username},
|
||||||
|
|
||||||
|
This is now your primary email address ({$new_address}). Going forward,
|
||||||
|
Phabricator will send all email here.
|
||||||
|
|
||||||
|
EOBODY;
|
||||||
|
|
||||||
|
id(new PhabricatorMetaMTAMail())
|
||||||
|
->addRawTos(array($new_address))
|
||||||
|
->setSubject('[Phabricator] Primary Address Changed')
|
||||||
|
->setBody($body)
|
||||||
|
->setFrom($user->getPHID())
|
||||||
|
->setRelatedPHID($user->getPHID())
|
||||||
|
->saveAndSend();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
src/applications/people/storage/email/__init__.php
Normal file
17
src/applications/people/storage/email/__init__.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/base');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'filesystem');
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhabricatorUserEmail.php');
|
|
@ -24,7 +24,6 @@ final class PhabricatorUser extends PhabricatorUserDAO {
|
||||||
protected $phid;
|
protected $phid;
|
||||||
protected $userName;
|
protected $userName;
|
||||||
protected $realName;
|
protected $realName;
|
||||||
protected $email;
|
|
||||||
protected $sex;
|
protected $sex;
|
||||||
protected $passwordSalt;
|
protected $passwordSalt;
|
||||||
protected $passwordHash;
|
protected $passwordHash;
|
||||||
|
@ -360,17 +359,30 @@ final class PhabricatorUser extends PhabricatorUserDAO {
|
||||||
$session_key);
|
$session_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateEmailToken($offset = 0) {
|
private function generateEmailToken(
|
||||||
|
PhabricatorUserEmail $email,
|
||||||
|
$offset = 0) {
|
||||||
|
|
||||||
|
$key = implode(
|
||||||
|
'-',
|
||||||
|
array(
|
||||||
|
PhabricatorEnv::getEnvConfig('phabricator.csrf-key'),
|
||||||
|
$this->getPHID(),
|
||||||
|
$email->getVerificationCode(),
|
||||||
|
));
|
||||||
|
|
||||||
return $this->generateToken(
|
return $this->generateToken(
|
||||||
time() + ($offset * self::EMAIL_CYCLE_FREQUENCY),
|
time() + ($offset * self::EMAIL_CYCLE_FREQUENCY),
|
||||||
self::EMAIL_CYCLE_FREQUENCY,
|
self::EMAIL_CYCLE_FREQUENCY,
|
||||||
PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(),
|
$key,
|
||||||
self::EMAIL_TOKEN_LENGTH);
|
self::EMAIL_TOKEN_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateEmailToken($token) {
|
public function validateEmailToken(
|
||||||
|
PhabricatorUserEmail $email,
|
||||||
|
$token) {
|
||||||
for ($ii = -1; $ii <= 1; $ii++) {
|
for ($ii = -1; $ii <= 1; $ii++) {
|
||||||
$valid = $this->generateEmailToken($ii);
|
$valid = $this->generateEmailToken($email, $ii);
|
||||||
if ($token == $valid) {
|
if ($token == $valid) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -378,11 +390,32 @@ final class PhabricatorUser extends PhabricatorUserDAO {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmailLoginURI() {
|
public function getEmailLoginURI(PhabricatorUserEmail $email = null) {
|
||||||
$token = $this->generateEmailToken();
|
if (!$email) {
|
||||||
|
$email = $this->loadPrimaryEmail();
|
||||||
|
if (!$email) {
|
||||||
|
throw new Exception("User has no primary email!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$token = $this->generateEmailToken($email);
|
||||||
$uri = PhabricatorEnv::getProductionURI('/login/etoken/'.$token.'/');
|
$uri = PhabricatorEnv::getProductionURI('/login/etoken/'.$token.'/');
|
||||||
$uri = new PhutilURI($uri);
|
$uri = new PhutilURI($uri);
|
||||||
return $uri->alter('email', $this->getEmail());
|
return $uri->alter('email', $email->getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadPrimaryEmailAddress() {
|
||||||
|
$email = $this->loadPrimaryEmail();
|
||||||
|
if (!$email) {
|
||||||
|
throw new Exception("User has no primary email address!");
|
||||||
|
}
|
||||||
|
return $email->getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadPrimaryEmail() {
|
||||||
|
return id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'userPHID = %s AND isPrimary = %d',
|
||||||
|
$this->getPHID(),
|
||||||
|
1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadPreferences() {
|
public function loadPreferences() {
|
||||||
|
@ -534,4 +567,16 @@ EOBODY;
|
||||||
return self::getDefaultProfileImageURI();
|
return self::getDefaultProfileImageURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function loadOneWithEmailAddress($address) {
|
||||||
|
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||||
|
'address = %s',
|
||||||
|
$address);
|
||||||
|
if (!$email) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return id(new PhabricatorUser())->loadOneWhere(
|
||||||
|
'phid = %s',
|
||||||
|
$email->getUserPHID());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/writeguard');
|
||||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||||
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
|
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/base');
|
phutil_require_module('phabricator', 'applications/people/storage/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/log');
|
phutil_require_module('phabricator', 'applications/people/storage/log');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/preferences');
|
phutil_require_module('phabricator', 'applications/people/storage/preferences');
|
||||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||||
|
|
|
@ -149,6 +149,13 @@ final class PhabricatorObjectHandleData {
|
||||||
$images = mpull($images, 'getBestURI', 'getPHID');
|
$images = mpull($images, 'getBestURI', 'getPHID');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This probably should not be part of Handles anymore, only
|
||||||
|
// MetaMTA actually uses it.
|
||||||
|
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
|
||||||
|
'userPHID IN (%Ls) AND isPrimary = 1',
|
||||||
|
$phids);
|
||||||
|
$emails = mpull($emails, 'getAddress', 'getUserPHID');
|
||||||
|
|
||||||
foreach ($phids as $phid) {
|
foreach ($phids as $phid) {
|
||||||
$handle = new PhabricatorObjectHandle();
|
$handle = new PhabricatorObjectHandle();
|
||||||
$handle->setPHID($phid);
|
$handle->setPHID($phid);
|
||||||
|
@ -159,7 +166,7 @@ final class PhabricatorObjectHandleData {
|
||||||
$user = $users[$phid];
|
$user = $users[$phid];
|
||||||
$handle->setName($user->getUsername());
|
$handle->setName($user->getUsername());
|
||||||
$handle->setURI('/p/'.$user->getUsername().'/');
|
$handle->setURI('/p/'.$user->getUsername().'/');
|
||||||
$handle->setEmail($user->getEmail());
|
$handle->setEmail(idx($emails, $phid));
|
||||||
$handle->setFullName(
|
$handle->setFullName(
|
||||||
$user->getUsername().' ('.$user->getRealName().')');
|
$user->getUsername().' ('.$user->getRealName().')');
|
||||||
$handle->setAlternateID($user->getID());
|
$handle->setAlternateID($user->getID());
|
||||||
|
|
|
@ -11,6 +11,7 @@ phutil_require_module('arcanist', 'differential/constants/revisionstatus');
|
||||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
|
phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||||
phutil_require_module('phabricator', 'applications/phid/handle');
|
phutil_require_module('phabricator', 'applications/phid/handle');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2011 Facebook, Inc.
|
* Copyright 2012 Facebook, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -103,9 +103,7 @@ abstract class PhabricatorRepositoryCommitMessageDetailParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findUserByEmailAddress($email_address) {
|
private function findUserByEmailAddress($email_address) {
|
||||||
$by_email = id(new PhabricatorUser())->loadOneWhere(
|
$by_email = PhabricatorUser::loadOneWithEmailAddress($email_address);
|
||||||
'email = %s',
|
|
||||||
$email_address);
|
|
||||||
if ($by_email) {
|
if ($by_email) {
|
||||||
return $by_email->getPHID();
|
return $by_email->getPHID();
|
||||||
}
|
}
|
||||||
|
|
|
@ -859,6 +859,18 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
||||||
'type' => 'sql',
|
'type' => 'sql',
|
||||||
'name' => $this->getPatchPath('userstatus.sql'),
|
'name' => $this->getPatchPath('userstatus.sql'),
|
||||||
),
|
),
|
||||||
|
'emailtable.sql' => array(
|
||||||
|
'type' => 'sql',
|
||||||
|
'name' => $this->getPatchPath('emailtable.sql'),
|
||||||
|
),
|
||||||
|
'emailtableport.sql' => array(
|
||||||
|
'type' => 'php',
|
||||||
|
'name' => $this->getPatchPath('emailtableport.php'),
|
||||||
|
),
|
||||||
|
'emailtableremove.sql' => array(
|
||||||
|
'type' => 'sql',
|
||||||
|
'name' => $this->getPatchPath('emailtableremove.sql'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue