Changeset View
Standalone View
src/applications/auth/constants/PhabricatorAuthFactorProviderStatus.php
- This file was added.
<?php | |||||
final class PhabricatorAuthFactorProviderStatus | |||||
extends Phobject { | |||||
private $key; | |||||
private $spec = array(); | |||||
const STATUS_ACTIVE = 'active'; | |||||
const STATUS_DEPRECATED = 'deprecated'; | |||||
const STATUS_DISABLED = 'disabled'; | |||||
public static function newForStatus($status) { | |||||
$result = new self(); | |||||
$result->key = $status; | |||||
$result->spec = self::newSpecification($status); | |||||
return $result; | |||||
} | |||||
public function getName() { | |||||
return idx($this->spec, 'name', $this->key); | |||||
} | |||||
public function getStatusHeaderIcon() { | |||||
return idx($this->spec, 'header.icon'); | |||||
} | |||||
public function getStatusHeaderColor() { | |||||
return idx($this->spec, 'header.color'); | |||||
} | |||||
public function isActive() { | |||||
return ($this->key === self::STATUS_ACTIVE); | |||||
} | |||||
public function getListIcon() { | |||||
return idx($this->spec, 'list.icon'); | |||||
} | |||||
public function getListColor() { | |||||
return idx($this->spec, 'list.color'); | |||||
} | |||||
public function getFactorIcon() { | |||||
return idx($this->spec, 'factor.icon'); | |||||
} | |||||
public function getFactorColor() { | |||||
return idx($this->spec, 'factor.color'); | |||||
} | |||||
public function getOrder() { | |||||
return idx($this->spec, 'order', 0); | |||||
} | |||||
public static function getMap() { | |||||
$specs = self::newSpecifications(); | |||||
return ipull($specs, 'name'); | |||||
} | |||||
private static function newSpecification($key) { | |||||
$specs = self::newSpecifications(); | |||||
return idx($specs, $key, array()); | |||||
} | |||||
private static function newSpecifications() { | |||||
return array( | |||||
self::STATUS_ACTIVE => array( | |||||
'name' => pht('Active'), | |||||
'header.icon' => 'fa-check', | |||||
'header.color' => null, | |||||
'list.icon' => null, | |||||
'list.color' => null, | |||||
'factor.icon' => 'fa-check', | |||||
'factor.color' => 'green', | |||||
'order' => 1, | |||||
), | |||||
self::STATUS_DEPRECATED => array( | |||||
'name' => pht('Deprecated'), | |||||
'header.icon' => 'fa-ban', | |||||
epriestley: I don't love this name (it means "No New Enrollments Under This Provider, But Existing Configs… | |||||
Not Done Inline ActionsSTATUS_SUNSETTING? Or we could get rid of this state by making the enrollment in a new provider much more in-your-face: you can't disable an existing provider until you configure a new one (or sign a waiver accepting that you're killing MFA for everyone), and then all users immediately get prompted to configure the new provider. Or we could just say that "slowly migrating to a new provider" (as opposed to a crisis migration because of a breach) is an edge case that isn't worth supporting, and prompt users at their next login/high-sec action to setup a new factor. amckinley: `STATUS_SUNSETTING`?
`STATUS_PLANNED_OBSOLESCENCE`?
Or we could get rid of this state by… | |||||
Done Inline ActionsI think this "migrate/sunset" step is at least somewhat valuable because jumping straight to "Disabled" is effectively the same as stripping the old factor -- if you move "TOTP" from "active" to "disabled", that's largely equivalent to just removing it from everyone's accounts. This might give an attacker a window to add MFA to an account without needing old MFA, since at the "MFA Setup Gate" they can just add a factor they control and then escalate their attack from there. If the change is "corporate policy", that seems possibly bad. Even if there was a breach, it seems possibly bad unless the nature of the breach was catastrophic and the factor is completely compromised to all attackers. If the breach wasn't a total compromise, it seems bad to say "anyone who got up from their desk for a minute to get coffee can now have adversarial MFA added to their account by an opportunistic attacker". (We do prevent you from disabling or deprecating the last active provider if security.require-multi-factor-auth is enabled, but not if it's disabled, and you can always UpDaTe ... yourself into that state or disable "Require MFA", disable all your providers, and then enable "Require MFA" to get yourself into a mess. epriestley: I think this "migrate/sunset" step is at least somewhat valuable because jumping straight to… | |||||
'header.color' => 'indigo', | |||||
'list.icon' => 'fa-ban', | |||||
'list.color' => 'indigo', | |||||
'factor.icon' => 'fa-ban', | |||||
'factor.color' => 'indigo', | |||||
'order' => 2, | |||||
), | |||||
self::STATUS_DISABLED => array( | |||||
'name' => pht('Disabled'), | |||||
'header.icon' => 'fa-times', | |||||
'header.color' => 'red', | |||||
'list.icon' => 'fa-times', | |||||
'list.color' => 'red', | |||||
'factor.icon' => 'fa-times', | |||||
'factor.color' => 'grey', | |||||
'order' => 3, | |||||
), | |||||
); | |||||
} | |||||
} |
I don't love this name (it means "No New Enrollments Under This Provider, But Existing Configs Will Continue to Work"), but can't come up with anything short and obviously-better. Currently planning to try to just document my way out of this.