1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-03-28 12:08:14 +01:00
phorge-phorge/src/applications/auth/xaction/PhabricatorAuthFactorProviderStatusTransaction.php
epriestley 8e5d9c6f0e Allow MFA providers to be deprecated or disabled
Summary: Ref T13222. Providers can now be deprecated (existing factors still work, but users can't add new factors for the provider) or disabled (factors stop working, also can't add new ones).

Test Plan:
  - Enabled, deprecated, and disabled some providers.
  - Viewed provider detail, provider list.
  - Viewed MFA settings list.
  - Verified that I'm prompted for enabled + deprecated only at gates.
  - Tried to disable final provider, got an error.
  - Hit the MFA setup gate by enabling "Require MFA" with no providers, got a more useful message.
  - Immediately forced a user to the "MFA Setup Gate" by disabling their only active provider with another provider enabled ("We no longer support TOTP, you HAVE to finish Duo enrollment to continue starting Monday.").

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D20031
2019-01-28 09:29:27 -08:00

103 lines
3.2 KiB
PHP

<?php
final class PhabricatorAuthFactorProviderStatusTransaction
extends PhabricatorAuthFactorProviderTransactionType {
const TRANSACTIONTYPE = 'status';
public function generateOldValue($object) {
return $object->getStatus();
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
$old_display = PhabricatorAuthFactorProviderStatus::newForStatus($old)
->getName();
$new_display = PhabricatorAuthFactorProviderStatus::newForStatus($new)
->getName();
return pht(
'%s changed the status of this provider from %s to %s.',
$this->renderAuthor(),
$this->renderValue($old_display),
$this->renderValue($new_display));
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$actor = $this->getActor();
$map = PhabricatorAuthFactorProviderStatus::getMap();
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
if (!isset($map[$new_value])) {
$errors[] = $this->newInvalidError(
pht(
'Status "%s" is invalid. Valid statuses are: %s.',
$new_value,
implode(', ', array_keys($map))),
$xaction);
continue;
}
$require_key = 'security.require-multi-factor-auth';
$require_mfa = PhabricatorEnv::getEnvConfig($require_key);
if ($require_mfa) {
$status_active = PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE;
if ($new_value !== $status_active) {
$active_providers = id(new PhabricatorAuthFactorProviderQuery())
->setViewer($actor)
->withStatuses(
array(
$status_active,
))
->execute();
$active_providers = mpull($active_providers, null, 'getID');
unset($active_providers[$object->getID()]);
if (!$active_providers) {
$errors[] = $this->newInvalidError(
pht(
'You can not deprecate or disable the last active MFA '.
'provider while "%s" is enabled, because new users would '.
'be unable to enroll in MFA. Disable the MFA requirement '.
'in Config, or create or enable another MFA provider first.',
$require_key));
continue;
}
}
}
}
return $errors;
}
public function didCommitTransaction($object, $value) {
$status = PhabricatorAuthFactorProviderStatus::newForStatus($value);
// If a provider has undergone a status change, reset the MFA enrollment
// cache for all users. This may immediately force a lot of users to redo
// MFA enrollment.
// We could be more surgical about this: we only really need to affect
// users who had a factor under the provider, and only really need to
// do anything if a provider was disabled. This is just a little simpler.
$table = new PhabricatorUser();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'UPDATE %R SET isEnrolledInMultiFactor = 0',
$table);
}
}