1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-04 20:52:43 +01:00
phorge-phorge/src/applications/auth/xaction/PhabricatorAuthContactNumberNumberTransaction.php
epriestley 7805b217ad Prevent users from editing, disabling, or swapping their primary contact number while they have SMS MFA
Summary:
Depends on D20022. Ref T13222. Since you can easily lock yourself out of your account by swapping to a bad number, prevent contact number edits while "contact number" MFA (today, always SMS) is enabled.

(Another approach would be to bind factors to specific contact numbers, and then prevent that number from being edited or disabled while SMS MFA was attached to it. However, I think that's a bit more complicated and a little more unwieldy, and ends up in about the same place as this. I'd consider it more strongly in the future if we had like 20 users say "I have 9 phones" but I doubt this is a real use case.)

Test Plan:
  - With SMS MFA, tried to edit my primary contact number, disable it, and promote another number to become primary. Got a sensible error message in all cases.
  - After removing SMS MFA, did all that stuff with no issues.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D20023
2019-01-23 14:18:33 -08:00

96 lines
2.5 KiB
PHP

<?php
final class PhabricatorAuthContactNumberNumberTransaction
extends PhabricatorAuthContactNumberTransactionType {
const TRANSACTIONTYPE = 'number';
public function generateOldValue($object) {
return $object->getContactNumber();
}
public function generateNewValue($object, $value) {
$number = new PhabricatorPhoneNumber($value);
return $number->toE164();
}
public function applyInternalEffects($object, $value) {
$object->setContactNumber($value);
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
return pht(
'%s changed this contact number from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$current_value = $object->getContactNumber();
if ($this->isEmptyTextTransaction($current_value, $xactions)) {
$errors[] = $this->newRequiredError(
pht('Contact numbers must have a contact number.'));
return $errors;
}
$max_length = $object->getColumnMaximumByteLength('contactNumber');
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
$new_length = strlen($new_value);
if ($new_length > $max_length) {
$errors[] = $this->newInvalidError(
pht(
'Contact numbers can not be longer than %s characters.',
new PhutilNumber($max_length)),
$xaction);
continue;
}
try {
new PhabricatorPhoneNumber($new_value);
} catch (Exception $ex) {
$errors[] = $this->newInvalidError(
pht(
'Contact number is invalid: %s',
$ex->getMessage()),
$xaction);
continue;
}
$new_value = $this->generateNewValue($object, $new_value);
$unique_key = id(clone $object)
->setContactNumber($new_value)
->newUniqueKey();
$other = id(new PhabricatorAuthContactNumberQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUniqueKeys(array($unique_key))
->executeOne();
if ($other) {
if ($other->getID() !== $object->getID()) {
$errors[] = $this->newInvalidError(
pht('Contact number is already in use.'),
$xaction);
continue;
}
}
$mfa_error = $this->newContactNumberMFAError($object, $xaction);
if ($mfa_error) {
$errors[] = $mfa_error;
continue;
}
}
return $errors;
}
}