mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-29 10:12:41 +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->setUsername($username);
|
||||
|
||||
$is_new = true;
|
||||
} else {
|
||||
$original = clone $user;
|
||||
|
||||
|
@ -66,6 +68,8 @@ if (!$user) {
|
|||
echo "Cancelled.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$is_new = false;
|
||||
}
|
||||
|
||||
$user_realname = $user->getRealName();
|
||||
|
@ -79,30 +83,28 @@ $realname = nonempty(
|
|||
$user_realname);
|
||||
$user->setRealName($realname);
|
||||
|
||||
$user_email = $user->getEmail();
|
||||
if (strlen($user_email)) {
|
||||
$email_prompt = ' ['.$user_email.']';
|
||||
} else {
|
||||
$email_prompt = '';
|
||||
}
|
||||
// When creating a new user we prompt for an email address; when editing an
|
||||
// existing user we just skip this because it would be quite involved to provide
|
||||
// a reasonable CLI interface for editing multiple addresses and managing email
|
||||
// verification and primary addresses.
|
||||
|
||||
do {
|
||||
$email = nonempty(
|
||||
phutil_console_prompt("Enter user email address{$email_prompt}:"),
|
||||
$user_email);
|
||||
$duplicate = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$email);
|
||||
if ($duplicate && $duplicate->getID() != $user->getID()) {
|
||||
$duplicate_username = $duplicate->getUsername();
|
||||
echo "ERROR: There is already a user with that email address ".
|
||||
"({$duplicate_username}). Each user must have a unique email ".
|
||||
"address.\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
$user->setEmail($email);
|
||||
$new_email = null;
|
||||
if ($is_new) {
|
||||
do {
|
||||
$email = phutil_console_prompt("Enter user email address:");
|
||||
$duplicate = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||
'address = %s',
|
||||
$email);
|
||||
if ($duplicate) {
|
||||
echo "ERROR: There is already a user with that email address. ".
|
||||
"Each user must have a unique email address.\n";
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
$new_email = $email;
|
||||
}
|
||||
|
||||
$changed_pass = false;
|
||||
// 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, 'Username', $original->getUsername(), $user->getUsername());
|
||||
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,
|
||||
($changed_pass !== false)
|
||||
? 'Updated'
|
||||
|
@ -153,4 +157,13 @@ if ($changed_pass !== false) {
|
|||
$user->save();
|
||||
}
|
||||
|
||||
if ($new_email) {
|
||||
id(new PhabricatorUserEmail())
|
||||
->setUserPHID($user->getPHID())
|
||||
->setAddress($new_email)
|
||||
->setIsVerified(1)
|
||||
->setIsPrimary(1)
|
||||
->save();
|
||||
}
|
||||
|
||||
echo "Saved changes.\n";
|
||||
|
|
|
@ -50,20 +50,26 @@ if ($existing_user) {
|
|||
"There is already a user with the username '{$username}'!");
|
||||
}
|
||||
|
||||
$existing_user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$existing_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||
'address = %s',
|
||||
$email);
|
||||
if ($existing_user) {
|
||||
if ($existing_email) {
|
||||
throw new Exception(
|
||||
"There is already a user with the email '{$email}'!");
|
||||
}
|
||||
|
||||
$user = new PhabricatorUser();
|
||||
$user->setUsername($username);
|
||||
$user->setEmail($email);
|
||||
$user->setRealname($realname);
|
||||
$user->save();
|
||||
|
||||
$email_object = id(new PhabricatorUserEmail())
|
||||
->setUserPHID($user->getPHID())
|
||||
->setAddress($email)
|
||||
->setIsVerified(1)
|
||||
->setIsPrimary(1)
|
||||
->save();
|
||||
|
||||
$user->sendWelcomeEmail($admin);
|
||||
|
||||
echo "Created user '{$username}' (realname='{$realname}', email='{$email}').\n";
|
||||
|
|
|
@ -595,6 +595,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEdgeQuery' => 'infrastructure/edges/query/edge',
|
||||
'PhabricatorEmailLoginController' => 'applications/auth/controller/email',
|
||||
'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken',
|
||||
'PhabricatorEmailVerificationController' => 'applications/people/controller/emailverification',
|
||||
'PhabricatorEnv' => 'infrastructure/env',
|
||||
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__',
|
||||
'PhabricatorErrorExample' => 'applications/uiexample/examples/error',
|
||||
|
@ -946,6 +947,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
|
||||
'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
|
||||
'PhabricatorUserDAO' => 'applications/people/storage/base',
|
||||
'PhabricatorUserEmail' => 'applications/people/storage/email',
|
||||
'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/emailpref',
|
||||
'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email',
|
||||
'PhabricatorUserLog' => 'applications/people/storage/log',
|
||||
|
@ -1534,6 +1536,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
|
||||
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
|
||||
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
|
||||
'PhabricatorEmailVerificationController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorEnvTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorErrorExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorEvent' => 'PhutilEvent',
|
||||
|
@ -1819,6 +1822,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||
'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorUserEmail' => 'PhabricatorUserDAO',
|
||||
'PhabricatorUserEmailPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||
'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||
'PhabricatorUserLog' => 'PhabricatorUserDAO',
|
||||
|
|
|
@ -433,6 +433,9 @@ class AphrontDefaultApplicationConfiguration
|
|||
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
|
||||
),
|
||||
),
|
||||
|
||||
'/emailverify/(?P<code>[^/]+)/' =>
|
||||
'PhabricatorEmailVerificationController',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ class AphrontRedirectResponse extends AphrontResponse {
|
|||
}
|
||||
|
||||
public function getURI() {
|
||||
return $this->uri;
|
||||
return (string)$this->uri;
|
||||
}
|
||||
|
||||
public function getHeaders() {
|
||||
|
|
|
@ -57,17 +57,24 @@ final class PhabricatorEmailLoginController
|
|||
// it expensive to fish for valid email addresses while giving the user
|
||||
// a better error if they goof their email.
|
||||
|
||||
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||
'address = %s',
|
||||
$email);
|
||||
|
||||
$target_user = null;
|
||||
if ($target_email) {
|
||||
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'phid = %s',
|
||||
$target_email->getUserPHID());
|
||||
}
|
||||
|
||||
if (!$target_user) {
|
||||
$errors[] = "There is no account associated with that email address.";
|
||||
$e_email = "Invalid";
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$uri = $target_user->getEmailLoginURI();
|
||||
$uri = $target_user->getEmailLoginURI($target_email);
|
||||
if ($is_serious) {
|
||||
$body = <<<EOBODY
|
||||
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', 'applications/auth/controller/base');
|
||||
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', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
|
|
|
@ -55,11 +55,31 @@ final class PhabricatorEmailTokenController
|
|||
$token = $this->token;
|
||||
$email = $request->getStr('email');
|
||||
|
||||
$target_user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
// NOTE: We need to bind verification to **addresses**, not **users**,
|
||||
// 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);
|
||||
|
||||
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->setHeader('Unable to Login');
|
||||
$view->appendChild(
|
||||
|
@ -71,19 +91,32 @@ final class PhabricatorEmailTokenController
|
|||
'<div class="aphront-failure-continue">'.
|
||||
'<a class="button" href="/login/email/">Send Another Email</a>'.
|
||||
'</div>');
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
$view,
|
||||
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');
|
||||
$request->setCookie('phusr', $target_user->getUsername());
|
||||
$request->setCookie('phsid', $session_key);
|
||||
|
||||
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 {
|
||||
$next = '/';
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
phutil_require_module('phabricator', 'aphront/response/400');
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
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', 'infrastructure/env');
|
||||
phutil_require_module('phabricator', 'view/page/failure');
|
||||
|
|
|
@ -113,9 +113,7 @@ final class PhabricatorLoginController
|
|||
$username_or_email);
|
||||
|
||||
if (!$user) {
|
||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$username_or_email);
|
||||
$user = PhabricatorUser::loadOneWithEmailAddress($username_or_email);
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
|
|
|
@ -176,8 +176,8 @@ final class PhabricatorOAuthLoginController
|
|||
|
||||
$oauth_email = $provider->retrieveUserEmail();
|
||||
if ($oauth_email) {
|
||||
$known_email = id(new PhabricatorUser())
|
||||
->loadOneWhere('email = %s', $oauth_email);
|
||||
$known_email = id(new PhabricatorUserEmail())
|
||||
->loadOneWhere('address = %s', $oauth_email);
|
||||
if ($known_email) {
|
||||
$dialog = new AphrontDialogView();
|
||||
$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/oauth/provider/base');
|
||||
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/useroauthinfo');
|
||||
phutil_require_module('phabricator', 'infrastructure/env');
|
||||
|
|
|
@ -33,7 +33,8 @@ final class PhabricatorOAuthDefaultRegistrationController
|
|||
|
||||
$user->setUsername($provider->retrieveUserAccountName());
|
||||
$user->setRealName($provider->retrieveUserRealName());
|
||||
$user->setEmail($provider->retrieveUserEmail());
|
||||
|
||||
$new_email = $provider->retrieveUserEmail();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
|
@ -49,9 +50,9 @@ final class PhabricatorOAuthDefaultRegistrationController
|
|||
$e_username = null;
|
||||
}
|
||||
|
||||
if ($user->getEmail() === null) {
|
||||
$user->setEmail($request->getStr('email'));
|
||||
if (!strlen($user->getEmail())) {
|
||||
if (!$new_email) {
|
||||
$new_email = trim($request->getStr('email'));
|
||||
if (!$new_email) {
|
||||
$e_email = 'Required';
|
||||
$errors[] = 'Email is required.';
|
||||
} else {
|
||||
|
@ -84,12 +85,29 @@ final class PhabricatorOAuthDefaultRegistrationController
|
|||
try {
|
||||
$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->save();
|
||||
|
||||
$session_key = $user->establishSession('web');
|
||||
$request->setCookie('phusr', $user->getUsername());
|
||||
$request->setCookie('phsid', $session_key);
|
||||
|
||||
$new_email->sendVerificationEmail($user);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI('/');
|
||||
} catch (AphrontQueryDuplicateKeyException $exception) {
|
||||
|
||||
|
@ -97,9 +115,9 @@ final class PhabricatorOAuthDefaultRegistrationController
|
|||
'userName = %s',
|
||||
$user->getUserName());
|
||||
|
||||
$same_email = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$user->getEmail());
|
||||
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||
'address = %s',
|
||||
$new_email);
|
||||
|
||||
if ($same_username) {
|
||||
$e_username = 'Duplicate';
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/auth/controller/oauthregistration/base');
|
||||
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', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||
|
|
|
@ -26,7 +26,6 @@ abstract class ConduitAPI_user_Method extends ConduitAPIMethod {
|
|||
'phid' => $user->getPHID(),
|
||||
'userName' => $user->getUserName(),
|
||||
'realName' => $user->getRealName(),
|
||||
'email' => $user->getEmail(),
|
||||
'image' => $user->loadProfileImageURI(),
|
||||
'uri' => PhabricatorEnv::getURI('/p/'.$user->getUsername().'/'),
|
||||
);
|
||||
|
|
|
@ -672,8 +672,7 @@ abstract class DifferentialFieldSpecification {
|
|||
$object_map = array();
|
||||
|
||||
$users = id(new PhabricatorUser())->loadAllWhere(
|
||||
'(username IN (%Ls)) OR (email IN (%Ls))',
|
||||
$value,
|
||||
'(username IN (%Ls))',
|
||||
$value);
|
||||
|
||||
$user_map = mpull($users, 'getPHID', 'getUsername');
|
||||
|
|
|
@ -108,6 +108,11 @@ final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function addRawTos(array $raw_email) {
|
||||
$this->setParam('raw-to', $raw_email);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCCs(array $phids) {
|
||||
$phids = array_unique($phids);
|
||||
$this->setParam('cc', $phids);
|
||||
|
@ -367,9 +372,12 @@ final class PhabricatorMetaMTAMail extends PhabricatorMetaMTADAO {
|
|||
$handles,
|
||||
$exclude);
|
||||
if ($emails) {
|
||||
$add_to = $emails;
|
||||
$add_to = array_merge($add_to, $emails);
|
||||
}
|
||||
break;
|
||||
case 'raw-to':
|
||||
$add_to = array_merge($add_to, $value);
|
||||
break;
|
||||
case 'cc':
|
||||
$emails = $this->getDeliverableEmailsFromHandles(
|
||||
$value,
|
||||
|
|
|
@ -274,9 +274,7 @@ final class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
|
|||
$from = idx($this->headers, 'from');
|
||||
$from = $this->getRawEmailAddress($from);
|
||||
|
||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$from);
|
||||
$user = PhabricatorUser::loadOneWithEmailAddress($from);
|
||||
|
||||
// If Phabricator is configured to allow "Reply-To" authentication, try
|
||||
// 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 = $this->getRawEmailAddress($reply_to);
|
||||
if ($reply_to) {
|
||||
$user = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$reply_to);
|
||||
$user = PhabricatorUser::loadOneWithEmailAddress($reply_to);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -123,13 +123,20 @@ final class PhabricatorPeopleEditController
|
|||
|
||||
$welcome_checked = true;
|
||||
|
||||
$new_email = null;
|
||||
|
||||
$request = $this->getRequest();
|
||||
if ($request->isFormPost()) {
|
||||
$welcome_checked = $request->getInt('welcome');
|
||||
|
||||
if (!$user->getID()) {
|
||||
$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') {
|
||||
$user->setIsSystemAgent(true);
|
||||
|
@ -154,13 +161,6 @@ final class PhabricatorPeopleEditController
|
|||
$e_realname = null;
|
||||
}
|
||||
|
||||
if (!strlen($user->getEmail())) {
|
||||
$errors[] = 'Email is required.';
|
||||
$e_email = 'Required';
|
||||
} else {
|
||||
$e_email = null;
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
try {
|
||||
$is_new = !$user->getID();
|
||||
|
@ -168,6 +168,14 @@ final class PhabricatorPeopleEditController
|
|||
$user->save();
|
||||
|
||||
if ($is_new) {
|
||||
|
||||
$email = id(new PhabricatorUserEmail())
|
||||
->setUserPHID($user->getPHID())
|
||||
->setAddress($new_email)
|
||||
->setIsPrimary(1)
|
||||
->setIsVerified(0)
|
||||
->save();
|
||||
|
||||
$log = PhabricatorUserLog::newLog(
|
||||
$admin,
|
||||
$user,
|
||||
|
@ -187,8 +195,8 @@ final class PhabricatorPeopleEditController
|
|||
|
||||
$same_username = id(new PhabricatorUser())
|
||||
->loadOneWhere('username = %s', $user->getUsername());
|
||||
$same_email = id(new PhabricatorUser())
|
||||
->loadOneWhere('email = %s', $user->getEmail());
|
||||
$same_email = id(new PhabricatorUserEmail())
|
||||
->loadOneWhere('address = %s', $new_email);
|
||||
|
||||
if ($same_username) {
|
||||
$e_username = 'Duplicate';
|
||||
|
@ -236,15 +244,19 @@ final class PhabricatorPeopleEditController
|
|||
->setLabel('Real Name')
|
||||
->setName('realname')
|
||||
->setValue($user->getRealName())
|
||||
->setError($e_realname))
|
||||
->appendChild(
|
||||
->setError($e_realname));
|
||||
|
||||
if (!$user->getID()) {
|
||||
$form->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Email')
|
||||
->setName('email')
|
||||
->setDisabled($is_immutable)
|
||||
->setValue($user->getEmail())
|
||||
->setError($e_email))
|
||||
->appendChild($this->getRoleInstructions());
|
||||
->setValue($new_email)
|
||||
->setError($e_email));
|
||||
}
|
||||
|
||||
$form->appendChild($this->getRoleInstructions());
|
||||
|
||||
if (!$user->getID()) {
|
||||
$form
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
phutil_require_module('phabricator', 'aphront/response/404');
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
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/user');
|
||||
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')
|
||||
->addSpacer()
|
||||
->addLabel('Email')
|
||||
->addFilter('email', 'Email Address')
|
||||
->addFilter('email', 'Email Addresses')
|
||||
->addFilter('emailpref', 'Email Preferences')
|
||||
->addSpacer()
|
||||
->addLabel('Authentication');
|
||||
|
|
|
@ -25,72 +25,317 @@ final class PhabricatorUserEmailSettingsPanelController
|
|||
$user = $request->getUser();
|
||||
$editable = $this->getAccountEditable();
|
||||
|
||||
$e_email = true;
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
if (!$editable) {
|
||||
return new Aphront400Response();
|
||||
$uri = $request->getRequestURI();
|
||||
$uri->setQueryParams(array());
|
||||
|
||||
if ($editable) {
|
||||
$new = $request->getStr('new');
|
||||
if ($new) {
|
||||
return $this->returnNewAddressResponse($uri, $new);
|
||||
}
|
||||
|
||||
$user->setEmail($request->getStr('email'));
|
||||
$delete = $request->getInt('delete');
|
||||
if ($delete) {
|
||||
return $this->returnDeleteAddressResponse($uri, $delete);
|
||||
}
|
||||
}
|
||||
|
||||
if (!strlen($user->getEmail())) {
|
||||
$errors[] = 'You must enter an e-mail address.';
|
||||
$verify = $request->getInt('verify');
|
||||
if ($verify) {
|
||||
return $this->returnVerifyAddressResponse($uri, $verify);
|
||||
}
|
||||
|
||||
$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 {
|
||||
if ($email->getIsVerified()) {
|
||||
$action = javelin_render_tag(
|
||||
'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;
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
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) {
|
||||
$user->save();
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/settings/page/email/?saved=true');
|
||||
$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)
|
||||
->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.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$notice = null;
|
||||
if (!$errors) {
|
||||
if ($request->getStr('saved')) {
|
||||
$notice = new AphrontErrorView();
|
||||
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
|
||||
$notice->setTitle('Changes Saved');
|
||||
$notice->appendChild('<p>Your changes have been saved.</p>');
|
||||
}
|
||||
} else {
|
||||
$notice = new AphrontErrorView();
|
||||
$notice->setTitle('Form Errors');
|
||||
$notice->setErrors($errors);
|
||||
if ($errors) {
|
||||
$errors = id(new AphrontErrorView())
|
||||
->setWidth(AphrontErrorView::WIDTH_DIALOG)
|
||||
->setErrors($errors);
|
||||
}
|
||||
|
||||
$form = new AphrontFormView();
|
||||
$form
|
||||
->setUser($user)
|
||||
$form = id(new AphrontFormLayoutView())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('Email')
|
||||
->setName('email')
|
||||
->setDisabled(!$editable)
|
||||
->setCaption(
|
||||
'Note: there is no email validation yet; double-check your '.
|
||||
'typing.')
|
||||
->setValue($user->getEmail())
|
||||
->setValue($request->getStr('email'))
|
||||
->setError($e_email));
|
||||
|
||||
if ($editable) {
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Save'));
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($user)
|
||||
->addHiddenInput('new', 'true')
|
||||
->setTitle('New Address')
|
||||
->appendChild($errors)
|
||||
->appendChild($form)
|
||||
->addSubmitButton('Save')
|
||||
->addCancelButton($uri);
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
private function returnDeleteAddressResponse(PhutilURI $uri, $email_id) {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
// NOTE: You can only delete your own email addresses, and you can not
|
||||
// delete your primary address.
|
||||
$email = id(new PhabricatorUserEmail())->loadOneWhere(
|
||||
'id = %d AND userPHID = %s AND isPrimary = 0',
|
||||
$email_id,
|
||||
$user->getPHID());
|
||||
|
||||
if (!$email) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Email Settings');
|
||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
$panel->appendChild($form);
|
||||
if ($request->isFormPost()) {
|
||||
$email->delete();
|
||||
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||
}
|
||||
|
||||
return id(new AphrontNullView())
|
||||
$address = $email->getAddress();
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($user)
|
||||
->addHiddenInput('delete', $email_id)
|
||||
->setTitle("Really delete address '{$address}'?")
|
||||
->appendChild(
|
||||
array(
|
||||
$notice,
|
||||
$panel,
|
||||
));
|
||||
'<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/reload');
|
||||
phutil_require_module('phabricator', 'applications/people/controller/settings/panels/base');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||
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/error');
|
||||
phutil_require_module('phabricator', 'view/form/layout');
|
||||
phutil_require_module('phabricator', 'view/layout/panel');
|
||||
phutil_require_module('phabricator', 'view/null');
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
|
||||
|
|
|
@ -40,10 +40,16 @@ final class PhabricatorUserPasswordSettingsPanelController
|
|||
// the workflow from a password reset email.
|
||||
|
||||
$token = $request->getStr('token');
|
||||
|
||||
$valid_token = false;
|
||||
if ($token) {
|
||||
$valid_token = $user->validateEmailToken($token);
|
||||
} else {
|
||||
$valid_token = false;
|
||||
$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;
|
||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/response/400');
|
|||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'aphront/writeguard');
|
||||
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', 'view/form/base');
|
||||
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 $userName;
|
||||
protected $realName;
|
||||
protected $email;
|
||||
protected $sex;
|
||||
protected $passwordSalt;
|
||||
protected $passwordHash;
|
||||
|
@ -360,17 +359,30 @@ final class PhabricatorUser extends PhabricatorUserDAO {
|
|||
$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(
|
||||
time() + ($offset * self::EMAIL_CYCLE_FREQUENCY),
|
||||
self::EMAIL_CYCLE_FREQUENCY,
|
||||
PhabricatorEnv::getEnvConfig('phabricator.csrf-key').$this->getEmail(),
|
||||
$key,
|
||||
self::EMAIL_TOKEN_LENGTH);
|
||||
}
|
||||
|
||||
public function validateEmailToken($token) {
|
||||
public function validateEmailToken(
|
||||
PhabricatorUserEmail $email,
|
||||
$token) {
|
||||
for ($ii = -1; $ii <= 1; $ii++) {
|
||||
$valid = $this->generateEmailToken($ii);
|
||||
$valid = $this->generateEmailToken($email, $ii);
|
||||
if ($token == $valid) {
|
||||
return true;
|
||||
}
|
||||
|
@ -378,11 +390,32 @@ final class PhabricatorUser extends PhabricatorUserDAO {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function getEmailLoginURI() {
|
||||
$token = $this->generateEmailToken();
|
||||
public function getEmailLoginURI(PhabricatorUserEmail $email = null) {
|
||||
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 = 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() {
|
||||
|
@ -534,4 +567,16 @@ EOBODY;
|
|||
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/metamta/storage/mail');
|
||||
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/preferences');
|
||||
phutil_require_module('phabricator', 'applications/phid/constants');
|
||||
|
|
|
@ -149,6 +149,13 @@ final class PhabricatorObjectHandleData {
|
|||
$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) {
|
||||
$handle = new PhabricatorObjectHandle();
|
||||
$handle->setPHID($phid);
|
||||
|
@ -159,7 +166,7 @@ final class PhabricatorObjectHandleData {
|
|||
$user = $users[$phid];
|
||||
$handle->setName($user->getUsername());
|
||||
$handle->setURI('/p/'.$user->getUsername().'/');
|
||||
$handle->setEmail($user->getEmail());
|
||||
$handle->setEmail(idx($emails, $phid));
|
||||
$handle->setFullName(
|
||||
$user->getUsername().' ('.$user->getRealName().')');
|
||||
$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/maniphest/constants/owner');
|
||||
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/phid/constants');
|
||||
phutil_require_module('phabricator', 'applications/phid/handle');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* 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.
|
||||
|
@ -103,9 +103,7 @@ abstract class PhabricatorRepositoryCommitMessageDetailParser {
|
|||
}
|
||||
|
||||
private function findUserByEmailAddress($email_address) {
|
||||
$by_email = id(new PhabricatorUser())->loadOneWhere(
|
||||
'email = %s',
|
||||
$email_address);
|
||||
$by_email = PhabricatorUser::loadOneWithEmailAddress($email_address);
|
||||
if ($by_email) {
|
||||
return $by_email->getPHID();
|
||||
}
|
||||
|
|
|
@ -859,6 +859,18 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => '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