1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 10:12:41 +01:00

Add a skeleton for configurable MFA provider types

Summary:
Ref T13222. Ref T13231. See PHI912. I'm planning to turn MFA providers into concrete objects, so you can disable and configure them.

Currently, we only support TOTP, which doesn't require any configuration, but other provider types (like Duo or Yubikey OTP) do require some configuration (server URIs, API keys, etc). TOTP //could// also have some configuration, like "bits of entropy" or "allowed window size" or whatever, if we want.

Add concrete objects for this and standard transaction / policy / query support. These objects don't do anything interesting yet and don't actually interact with MFA, this is just skeleton code for now.

Test Plan:
{F6090444}

{F6090445}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13231, T13222

Differential Revision: https://secure.phabricator.com/D19935
This commit is contained in:
epriestley 2018-12-28 16:52:00 -08:00
parent b98d46ce7d
commit a62f334d95
23 changed files with 871 additions and 40 deletions

View file

@ -0,0 +1,9 @@
CREATE TABLE {$NAMESPACE}_auth.auth_factorprovider (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
providerFactorKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_auth.auth_factorprovidertransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL,
oldValue LONGTEXT NOT NULL,
newValue LONGTEXT NOT NULL,
contentSource LONGTEXT NOT NULL,
metadata LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_factorprovider
ADD name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -2190,6 +2190,7 @@ phutil_register_library_map(array(
'PhabricatorAuthAccountView' => 'applications/auth/view/PhabricatorAuthAccountView.php', 'PhabricatorAuthAccountView' => 'applications/auth/view/PhabricatorAuthAccountView.php',
'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php', 'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php',
'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php', 'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php',
'PhabricatorAuthAuthFactorProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorProviderPHIDType.php',
'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php', 'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php',
'PhabricatorAuthCSRFEngine' => 'applications/auth/engine/PhabricatorAuthCSRFEngine.php', 'PhabricatorAuthCSRFEngine' => 'applications/auth/engine/PhabricatorAuthCSRFEngine.php',
'PhabricatorAuthChallenge' => 'applications/auth/storage/PhabricatorAuthChallenge.php', 'PhabricatorAuthChallenge' => 'applications/auth/storage/PhabricatorAuthChallenge.php',
@ -2207,6 +2208,18 @@ phutil_register_library_map(array(
'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php', 'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php',
'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php', 'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php',
'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php', 'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php',
'PhabricatorAuthFactorProvider' => 'applications/auth/storage/PhabricatorAuthFactorProvider.php',
'PhabricatorAuthFactorProviderController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderController.php',
'PhabricatorAuthFactorProviderEditController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php',
'PhabricatorAuthFactorProviderEditEngine' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php',
'PhabricatorAuthFactorProviderEditor' => 'applications/auth/editor/PhabricatorAuthFactorProviderEditor.php',
'PhabricatorAuthFactorProviderListController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php',
'PhabricatorAuthFactorProviderNameTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderNameTransaction.php',
'PhabricatorAuthFactorProviderQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderQuery.php',
'PhabricatorAuthFactorProviderTransaction' => 'applications/auth/storage/PhabricatorAuthFactorProviderTransaction.php',
'PhabricatorAuthFactorProviderTransactionQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderTransactionQuery.php',
'PhabricatorAuthFactorProviderTransactionType' => 'applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php',
'PhabricatorAuthFactorProviderViewController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php',
'PhabricatorAuthFactorResult' => 'applications/auth/factor/PhabricatorAuthFactorResult.php', 'PhabricatorAuthFactorResult' => 'applications/auth/factor/PhabricatorAuthFactorResult.php',
'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php', 'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php',
'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php', 'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php',
@ -2277,6 +2290,7 @@ phutil_register_library_map(array(
'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php', 'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php',
'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php', 'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php',
'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php',
'PhabricatorAuthProviderController' => 'applications/auth/controller/config/PhabricatorAuthProviderController.php',
'PhabricatorAuthProvidersGuidanceContext' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php', 'PhabricatorAuthProvidersGuidanceContext' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php',
'PhabricatorAuthProvidersGuidanceEngineExtension' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php', 'PhabricatorAuthProvidersGuidanceEngineExtension' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php',
'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php',
@ -7835,6 +7849,7 @@ phutil_register_library_map(array(
'PhabricatorAuthAccountView' => 'AphrontView', 'PhabricatorAuthAccountView' => 'AphrontView',
'PhabricatorAuthApplication' => 'PhabricatorApplication', 'PhabricatorAuthApplication' => 'PhabricatorApplication',
'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthAuthFactorProviderPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType',
'PhabricatorAuthCSRFEngine' => 'Phobject', 'PhabricatorAuthCSRFEngine' => 'Phobject',
'PhabricatorAuthChallenge' => array( 'PhabricatorAuthChallenge' => array(
@ -7855,6 +7870,23 @@ phutil_register_library_map(array(
'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController',
'PhabricatorAuthFactor' => 'Phobject', 'PhabricatorAuthFactor' => 'Phobject',
'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO', 'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO',
'PhabricatorAuthFactorProvider' => array(
'PhabricatorAuthDAO',
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
),
'PhabricatorAuthFactorProviderController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthFactorProviderEditController' => 'PhabricatorAuthFactorProviderController',
'PhabricatorAuthFactorProviderEditEngine' => 'PhabricatorEditEngine',
'PhabricatorAuthFactorProviderEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthFactorProviderListController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthFactorProviderNameTransaction' => 'PhabricatorAuthFactorProviderTransactionType',
'PhabricatorAuthFactorProviderQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthFactorProviderTransaction' => 'PhabricatorModularTransaction',
'PhabricatorAuthFactorProviderTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthFactorProviderTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorAuthFactorProviderViewController' => 'PhabricatorAuthFactorProviderController',
'PhabricatorAuthFactorResult' => 'Phobject', 'PhabricatorAuthFactorResult' => 'Phobject',
'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase', 'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase',
'PhabricatorAuthFinishController' => 'PhabricatorAuthController', 'PhabricatorAuthFinishController' => 'PhabricatorAuthController',
@ -7931,11 +7963,12 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface', 'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
), ),
'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthController', 'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthProviderController',
'PhabricatorAuthProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuthProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthProviderController' => 'PhabricatorAuthController',
'PhabricatorAuthProvidersGuidanceContext' => 'PhabricatorGuidanceContext', 'PhabricatorAuthProvidersGuidanceContext' => 'PhabricatorGuidanceContext',
'PhabricatorAuthProvidersGuidanceEngineExtension' => 'PhabricatorGuidanceEngineExtension', 'PhabricatorAuthProvidersGuidanceEngineExtension' => 'PhabricatorGuidanceEngineExtension',
'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod',

View file

@ -85,6 +85,15 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController', 'view/(?P<id>\d+)/' => 'PhabricatorAuthSSHKeyViewController',
), ),
'password/' => 'PhabricatorAuthSetPasswordController', 'password/' => 'PhabricatorAuthSetPasswordController',
'mfa/' => array(
$this->getQueryRoutePattern() =>
'PhabricatorAuthFactorProviderListController',
$this->getEditRoutePattern('edit/') =>
'PhabricatorAuthFactorProviderEditController',
'(?P<id>[1-9]\d*)/' =>
'PhabricatorAuthFactorProviderViewController',
),
), ),
'/oauth/(?P<provider>\w+)/login/' '/oauth/(?P<provider>\w+)/login/'

View file

@ -91,7 +91,7 @@ final class PhabricatorAuthListController
pht('Add Authentication Provider')))); pht('Add Authentication Provider'))));
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Auth Providers')); $crumbs->addTextCrumb(pht('Login and Registration'));
$crumbs->setBorder(true); $crumbs->setBorder(true);
$guidance_context = new PhabricatorAuthProvidersGuidanceContext(); $guidance_context = new PhabricatorAuthProvidersGuidanceContext();
@ -102,12 +102,12 @@ final class PhabricatorAuthListController
->newInfoView(); ->newInfoView();
$button = id(new PHUIButtonView()) $button = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)
->setHref($this->getApplicationURI('config/new/')) ->setHref($this->getApplicationURI('config/new/'))
->setIcon('fa-plus') ->setIcon('fa-plus')
->setDisabled(!$can_manage) ->setDisabled(!$can_manage)
->setText(pht('Add Provider')); ->setText(pht('Add Provider'));
$list->setFlush(true); $list->setFlush(true);
$list = id(new PHUIObjectBoxView()) $list = id(new PHUIObjectBoxView())
@ -115,7 +115,7 @@ final class PhabricatorAuthListController
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list); ->appendChild($list);
$title = pht('Auth Providers'); $title = pht('Login and Registration Providers');
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader($title) ->setHeader($title)
->setHeaderIcon('fa-key') ->setHeaderIcon('fa-key')
@ -128,10 +128,15 @@ final class PhabricatorAuthListController
$list, $list,
)); ));
return $this->newPage() $nav = $this->newNavigation()
->setTitle($title)
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild($view); ->appendChild($view);
$nav->selectFilter('login');
return $this->newPage()
->setTitle($title)
->appendChild($nav);
} }
} }

View file

@ -1,32 +1,4 @@
<?php <?php
abstract class PhabricatorAuthProviderConfigController abstract class PhabricatorAuthProviderConfigController
extends PhabricatorAuthController { extends PhabricatorAuthProviderController {}
protected function buildSideNavView($for_app = false) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
if ($for_app) {
$nav->addLabel(pht('Create'));
$nav->addFilter('',
pht('Add Authentication Provider'),
$this->getApplicationURI('/config/new/'));
}
return $nav;
}
public function buildApplicationMenu() {
return $this->buildSideNavView($for_app = true)->getMenu();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
return $crumbs;
}
}

View file

@ -0,0 +1,43 @@
<?php
abstract class PhabricatorAuthProviderController
extends PhabricatorAuthController {
protected function newNavigation() {
$viewer = $this->getViewer();
$nav = id(new AphrontSideNavFilterView())
->setBaseURI(new PhutilURI($this->getApplicationURI()))
->setViewer($viewer);
$nav->addMenuItem(
id(new PHUIListItemView())
->setName(pht('Authentication'))
->setType(PHUIListItemView::TYPE_LABEL));
$nav->addMenuItem(
id(new PHUIListItemView())
->setKey('login')
->setName(pht('Login and Registration'))
->setType(PHUIListItemView::TYPE_LINK)
->setHref($this->getApplicationURI('/'))
->setIcon('fa-key'));
$nav->addMenuItem(
id(new PHUIListItemView())
->setKey('mfa')
->setName(pht('Multi-Factor'))
->setType(PHUIListItemView::TYPE_LINK)
->setHref($this->getApplicationURI('mfa/'))
->setIcon('fa-mobile'));
$nav->selectFilter(null);
return $nav;
}
public function buildApplicationMenu() {
return $this->newNavigation()->getMenu();
}
}

View file

@ -0,0 +1,11 @@
<?php
abstract class PhabricatorAuthFactorProviderController
extends PhabricatorAuthProviderController {
protected function buildApplicationCrumbs() {
return parent::buildApplicationCrumbs()
->addTextCrumb(pht('Multi-Factor'), $this->getApplicationURI('mfa/'));
}
}

View file

@ -0,0 +1,65 @@
<?php
final class PhabricatorAuthFactorProviderEditController
extends PhabricatorAuthFactorProviderController {
public function handleRequest(AphrontRequest $request) {
$this->requireApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$engine = id(new PhabricatorAuthFactorProviderEditEngine())
->setController($this);
$id = $request->getURIData('id');
if (!$id) {
$factor_key = $request->getStr('providerFactorKey');
$map = PhabricatorAuthFactor::getAllFactors();
$factor = idx($map, $factor_key);
if (!$factor) {
return $this->buildFactorSelectionResponse();
}
$engine
->addContextParameter('providerFactorKey', $factor_key)
->setProviderFactor($factor);
}
return $engine->buildResponse();
}
private function buildFactorSelectionResponse() {
$request = $this->getRequest();
$viewer = $this->getViewer();
$cancel_uri = $this->getApplicationURI('mfa/');
$factors = PhabricatorAuthFactor::getAllFactors();
$menu = id(new PHUIObjectItemListView())
->setUser($viewer)
->setBig(true)
->setFlush(true);
foreach ($factors as $factor_key => $factor) {
$factor_uri = id(new PhutilURI('/mfa/edit/'))
->setQueryParam('providerFactorKey', $factor_key);
$factor_uri = $this->getApplicationURI($factor_uri);
$item = id(new PHUIObjectItemView())
->setHeader($factor->getFactorName())
->setHref($factor_uri)
->setClickable(true)
->setImageIcon($factor->newIconView())
->addAttribute($factor->getFactorCreateHelp());
$menu->addItem($item);
}
return $this->newDialog()
->setTitle(pht('Choose Provider Type'))
->appendChild($menu)
->addCancelButton($cancel_uri);
}
}

View file

@ -0,0 +1,72 @@
<?php
final class PhabricatorAuthFactorProviderListController
extends PhabricatorAuthProviderController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$can_manage = $this->hasApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$providers = id(new PhabricatorAuthFactorProviderQuery())
->setViewer($viewer)
->execute();
$list = new PHUIObjectItemListView();
foreach ($providers as $provider) {
$item = id(new PHUIObjectItemView())
->setObjectName($provider->getObjectName())
->setHeader($provider->getDisplayName())
->setHref($provider->getURI());
$list->addItem($item);
}
$list->setNoDataString(
pht('You have not configured any multi-factor providers yet.'));
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Multi-Factor'))
->setBorder(true);
$button = id(new PHUIButtonView())
->setTag('a')
->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE)
->setHref($this->getApplicationURI('mfa/edit/'))
->setIcon('fa-plus')
->setDisabled(!$can_manage)
->setWorkflow(true)
->setText(pht('Add MFA Provider'));
$list->setFlush(true);
$list = id(new PHUIObjectBoxView())
->setHeaderText(pht('MFA Providers'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
$title = pht('MFA Providers');
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-mobile')
->addActionLink($button);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$list,
));
$nav = $this->newNavigation()
->setCrumbs($crumbs)
->appendChild($view);
$nav->selectFilter('mfa');
return $this->newPage()
->setTitle($title)
->appendChild($nav);
}
}

View file

@ -0,0 +1,100 @@
<?php
final class PhabricatorAuthFactorProviderViewController
extends PhabricatorAuthFactorProviderController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$this->requireApplicationCapability(
AuthManageProvidersCapability::CAPABILITY);
$provider = id(new PhabricatorAuthFactorProviderQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->executeOne();
if (!$provider) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($provider->getObjectName())
->setBorder(true);
$header = $this->buildHeaderView($provider);
$properties = $this->buildPropertiesView($provider);
$curtain = $this->buildCurtain($provider);
$timeline = $this->buildTransactionTimeline(
$provider,
new PhabricatorAuthFactorProviderTransactionQuery());
$timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$timeline,
))
->addPropertySection(pht('Details'), $properties);
return $this->newPage()
->setTitle($provider->getDisplayName())
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$provider->getPHID(),
))
->appendChild($view);
}
private function buildHeaderView(PhabricatorAuthFactorProvider $provider) {
$viewer = $this->getViewer();
$view = id(new PHUIHeaderView())
->setViewer($viewer)
->setHeader($provider->getDisplayName())
->setPolicyObject($provider);
return $view;
}
private function buildPropertiesView(
PhabricatorAuthFactorProvider $provider) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setViewer($viewer);
$view->addProperty(
pht('Factor Type'),
$provider->getFactor()->getFactorName());
return $view;
}
private function buildCurtain(PhabricatorAuthFactorProvider $provider) {
$viewer = $this->getViewer();
$id = $provider->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$provider,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain = $this->newCurtainView($provider);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit MFA Provider'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("mfa/edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $curtain;
}
}

View file

@ -0,0 +1,115 @@
<?php
final class PhabricatorAuthFactorProviderEditEngine
extends PhabricatorEditEngine {
private $providerFactor;
const ENGINECONST = 'auth.factor.provider';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('MFA Providers');
}
public function getSummaryHeader() {
return pht('Edit MFA Providers');
}
public function getSummaryText() {
return pht('This engine is used to edit MFA providers.');
}
public function getEngineApplicationClass() {
return 'PhabricatorAuthApplication';
}
public function setProviderFactor(PhabricatorAuthFactor $factor) {
$this->providerFactor = $factor;
return $this;
}
public function getProviderFactor() {
return $this->providerFactor;
}
protected function newEditableObject() {
$factor = $this->getProviderFactor();
if ($factor) {
$provider = PhabricatorAuthFactorProvider::initializeNewProvider($factor);
} else {
$provider = new PhabricatorAuthFactorProvider();
}
return $provider;
}
protected function newObjectQuery() {
return new PhabricatorAuthFactorProviderQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create MFA Provider');
}
protected function getObjectCreateButtonText($object) {
return pht('Create MFA Provider');
}
protected function getObjectEditTitleText($object) {
return pht('Edit MFA Provider');
}
protected function getObjectEditShortText($object) {
return $object->getObjectName();
}
protected function getObjectCreateShortText() {
return pht('Create MFA Provider');
}
protected function getObjectName() {
return pht('MFA Provider');
}
protected function getEditorURI() {
return '/auth/mfa/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/auth/mfa/';
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
AuthManageProvidersCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
$factor_name = $object->getFactor()->getFactorName();
return array(
id(new PhabricatorStaticEditField())
->setKey('displayType')
->setLabel(pht('Factor Type'))
->setDescription(pht('Type of the MFA provider.'))
->setValue($factor_name),
id(new PhabricatorTextEditField())
->setKey('name')
->setTransactionType(
PhabricatorAuthFactorProviderNameTransaction::TRANSACTIONTYPE)
->setLabel(pht('Name'))
->setDescription(pht('Display name for the MFA provider.'))
->setValue($object->getName())
->setPlaceholder($factor_name),
);
}
}

View file

@ -0,0 +1,22 @@
<?php
final class PhabricatorAuthFactorProviderEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAuthApplication';
}
public function getEditorObjectsDescription() {
return pht('MFA Providers');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this MFA provider.', $author);
}
public function getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
}

View file

@ -4,6 +4,7 @@ abstract class PhabricatorAuthFactor extends Phobject {
abstract public function getFactorName(); abstract public function getFactorName();
abstract public function getFactorKey(); abstract public function getFactorKey();
abstract public function getFactorCreateHelp();
abstract public function getFactorDescription(); abstract public function getFactorDescription();
abstract public function processAddFactorForm( abstract public function processAddFactorForm(
AphrontFormView $form, AphrontFormView $form,
@ -39,6 +40,11 @@ abstract class PhabricatorAuthFactor extends Phobject {
return new PhabricatorAuthFactorResult(); return new PhabricatorAuthFactorResult();
} }
public function newIconView() {
return id(new PHUIIconView())
->setIcon('fa-mobile');
}
protected function newChallenge( protected function newChallenge(
PhabricatorAuthFactorConfig $config, PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer) { PhabricatorUser $viewer) {

View file

@ -12,6 +12,12 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
return pht('Mobile Phone App (TOTP)'); return pht('Mobile Phone App (TOTP)');
} }
public function getFactorCreateHelp() {
return pht(
'Allow users to attach a mobile authenticator application (like '.
'Google Authenticator) to their account.');
}
public function getFactorDescription() { public function getFactorDescription() {
return pht( return pht(
'Attach a mobile authenticator application (like Authy '. 'Attach a mobile authenticator application (like Authy '.

View file

@ -0,0 +1,40 @@
<?php
final class PhabricatorAuthAuthFactorProviderPHIDType
extends PhabricatorPHIDType {
const TYPECONST = 'FPRV';
public function getTypeName() {
return pht('MFA Provider');
}
public function newObject() {
return new PhabricatorAuthFactorProvider();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorAuthApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorAuthFactorProviderQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$provider = $objects[$phid];
$handle->setURI($provider->getURI());
}
}
}

View file

@ -0,0 +1,67 @@
<?php
final class PhabricatorAuthFactorProviderQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function newResultObject() {
return new PhabricatorAuthFactorProvider();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
return $where;
}
protected function willFilterPage(array $providers) {
$map = PhabricatorAuthFactor::getAllFactors();
foreach ($providers as $key => $provider) {
$factor_key = $provider->getProviderFactorKey();
$factor = idx($map, $factor_key);
if (!$factor) {
unset($providers[$key]);
continue;
}
$provider->attachFactor($factor);
}
return $providers;
}
public function getQueryApplicationClass() {
return 'PhabricatorAuthApplication';
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorAuthFactorProviderTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhabricatorAuthFactorProviderTransaction();
}
}

View file

@ -0,0 +1,134 @@
<?php
final class PhabricatorAuthFactorProvider
extends PhabricatorAuthDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface {
protected $providerFactorKey;
protected $name;
protected $status;
protected $properties = array();
private $factor = self::ATTACHABLE;
const STATUS_ACTIVE = 'active';
const STATUS_DEPRECATED = 'deprecated';
const STATUS_DISABLED = 'disabled';
public static function initializeNewProvider(PhabricatorAuthFactor $factor) {
return id(new self())
->setProviderFactorKey($factor->getFactorKey())
->attachFactor($factor)
->setStatus(self::STATUS_ACTIVE);
}
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'providerFactorKey' => 'text64',
'name' => 'text255',
'status' => 'text32',
),
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorAuthAuthFactorProviderPHIDType::TYPECONST;
}
public function getURI() {
return '/auth/mfa/'.$this->getID().'/';
}
public function getObjectName() {
return pht('MFA Provider %d', $this->getID());
}
public function getAuthFactorProviderProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setAuthFactorProviderProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function attachFactor(PhabricatorAuthFactor $factor) {
$this->factor = $factor;
return $this;
}
public function getFactor() {
return $this->assertAttached($this->factor);
}
public function getDisplayName() {
$name = $this->getName();
if (strlen($name)) {
return $name;
}
return $this->getFactor()->getFactorName();
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorAuthFactorProviderEditor();
}
public function getApplicationTransactionTemplate() {
return new PhabricatorAuthFactorProviderTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
$extended = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
break;
case PhabricatorPolicyCapability::CAN_EDIT:
$extended[] = array(
new PhabricatorAuthApplication(),
AuthManageProvidersCapability::CAPABILITY,
);
break;
}
return $extended;
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhabricatorAuthFactorProviderTransaction
extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'auth';
}
public function getApplicationTransactionType() {
return PhabricatorAuthAuthFactorProviderPHIDType::TYPECONST;
}
public function getBaseTransactionClass() {
return 'PhabricatorAuthFactorProviderTransactionType';
}
}

View file

@ -0,0 +1,69 @@
<?php
final class PhabricatorAuthFactorProviderNameTransaction
extends PhabricatorAuthFactorProviderTransactionType {
const TRANSACTIONTYPE = 'name';
public function generateOldValue($object) {
return $object->getName();
}
public function applyInternalEffects($object, $value) {
$object->setName($value);
}
public function getTitle() {
$old = $this->getOldValue();
$new = $this->getNewValue();
if (!strlen($old)) {
return pht(
'%s named this provider %s.',
$this->renderAuthor(),
$this->renderNewValue());
} else if (!strlen($new)) {
return pht(
'%s removed the name (%s) of this provider.',
$this->renderAuthor(),
$this->renderOldValue());
} else {
return pht(
'%s renamed this provider from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
}
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$max_length = $object->getColumnMaximumByteLength('name');
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
$new_length = strlen($new_value);
if ($new_length > $max_length) {
$errors[] = $this->newInvalidError(
pht(
'Provider names can not be longer than %s characters.',
new PhutilNumber($max_length)),
$xaction);
}
}
return $errors;
}
public function getTransactionTypeForConduit($xaction) {
return 'name';
}
public function getFieldValuesForConduit($xaction, $data) {
return array(
'old' => $xaction->getOldValue(),
'new' => $xaction->getNewValue(),
);
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorAuthFactorProviderTransactionType
extends PhabricatorModularTransactionType {}