diff --git a/resources/sql/autopatches/20181228.auth.01.provider.sql b/resources/sql/autopatches/20181228.auth.01.provider.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20181228.auth.01.provider.sql @@ -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}; diff --git a/resources/sql/autopatches/20181228.auth.02.xaction.sql b/resources/sql/autopatches/20181228.auth.02.xaction.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20181228.auth.02.xaction.sql @@ -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}; diff --git a/resources/sql/autopatches/20181228.auth.03.name.sql b/resources/sql/autopatches/20181228.auth.03.name.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20181228.auth.03.name.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_factorprovider + ADD name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2190,6 +2190,7 @@ 'PhabricatorAuthAccountView' => 'applications/auth/view/PhabricatorAuthAccountView.php', 'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php', 'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php', + 'PhabricatorAuthAuthFactorProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorProviderPHIDType.php', 'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php', 'PhabricatorAuthCSRFEngine' => 'applications/auth/engine/PhabricatorAuthCSRFEngine.php', 'PhabricatorAuthChallenge' => 'applications/auth/storage/PhabricatorAuthChallenge.php', @@ -2207,6 +2208,18 @@ 'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php', 'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.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', 'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php', 'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php', @@ -2277,6 +2290,7 @@ 'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php', 'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php', 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', + 'PhabricatorAuthProviderController' => 'applications/auth/controller/config/PhabricatorAuthProviderController.php', 'PhabricatorAuthProvidersGuidanceContext' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php', 'PhabricatorAuthProvidersGuidanceEngineExtension' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php', @@ -7835,6 +7849,7 @@ 'PhabricatorAuthAccountView' => 'AphrontView', 'PhabricatorAuthApplication' => 'PhabricatorApplication', 'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorAuthAuthFactorProviderPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthCSRFEngine' => 'Phobject', 'PhabricatorAuthChallenge' => array( @@ -7855,6 +7870,23 @@ 'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthFactor' => 'Phobject', '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', 'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase', 'PhabricatorAuthFinishController' => 'PhabricatorAuthController', @@ -7931,11 +7963,12 @@ 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), - 'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthController', + 'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthProviderController', 'PhabricatorAuthProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorAuthProviderController' => 'PhabricatorAuthController', 'PhabricatorAuthProvidersGuidanceContext' => 'PhabricatorGuidanceContext', 'PhabricatorAuthProvidersGuidanceEngineExtension' => 'PhabricatorGuidanceEngineExtension', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod', diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -85,6 +85,15 @@ 'view/(?P\d+)/' => 'PhabricatorAuthSSHKeyViewController', ), 'password/' => 'PhabricatorAuthSetPasswordController', + + 'mfa/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorAuthFactorProviderListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorAuthFactorProviderEditController', + '(?P[1-9]\d*)/' => + 'PhabricatorAuthFactorProviderViewController', + ), ), '/oauth/(?P\w+)/login/' diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -91,7 +91,7 @@ pht('Add Authentication Provider')))); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Auth Providers')); + $crumbs->addTextCrumb(pht('Login and Registration')); $crumbs->setBorder(true); $guidance_context = new PhabricatorAuthProvidersGuidanceContext(); @@ -102,12 +102,12 @@ ->newInfoView(); $button = id(new PHUIButtonView()) - ->setTag('a') - ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) - ->setHref($this->getApplicationURI('config/new/')) - ->setIcon('fa-plus') - ->setDisabled(!$can_manage) - ->setText(pht('Add Provider')); + ->setTag('a') + ->setButtonType(PHUIButtonView::BUTTONTYPE_SIMPLE) + ->setHref($this->getApplicationURI('config/new/')) + ->setIcon('fa-plus') + ->setDisabled(!$can_manage) + ->setText(pht('Add Provider')); $list->setFlush(true); $list = id(new PHUIObjectBoxView()) @@ -115,7 +115,7 @@ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($list); - $title = pht('Auth Providers'); + $title = pht('Login and Registration Providers'); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setHeaderIcon('fa-key') @@ -128,10 +128,15 @@ $list, )); - return $this->newPage() - ->setTitle($title) + $nav = $this->newNavigation() ->setCrumbs($crumbs) ->appendChild($view); + + $nav->selectFilter('login'); + + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthProviderConfigController.php b/src/applications/auth/controller/config/PhabricatorAuthProviderConfigController.php --- a/src/applications/auth/controller/config/PhabricatorAuthProviderConfigController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthProviderConfigController.php @@ -1,32 +1,4 @@ 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; - } - -} + extends PhabricatorAuthProviderController {} diff --git a/src/applications/auth/controller/config/PhabricatorAuthProviderController.php b/src/applications/auth/controller/config/PhabricatorAuthProviderController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/config/PhabricatorAuthProviderController.php @@ -0,0 +1,43 @@ +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(); + } + +} diff --git a/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderController.php b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderController.php @@ -0,0 +1,11 @@ +addTextCrumb(pht('Multi-Factor'), $this->getApplicationURI('mfa/')); + } + +} diff --git a/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderEditController.php @@ -0,0 +1,65 @@ +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); + } + +} diff --git a/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php @@ -0,0 +1,72 @@ +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); + } + +} diff --git a/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php @@ -0,0 +1,100 @@ +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; + } + +} diff --git a/src/applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php b/src/applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php @@ -0,0 +1,115 @@ +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), + ); + } + +} diff --git a/src/applications/auth/editor/PhabricatorAuthFactorProviderEditor.php b/src/applications/auth/editor/PhabricatorAuthFactorProviderEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/editor/PhabricatorAuthFactorProviderEditor.php @@ -0,0 +1,22 @@ +setIcon('fa-mobile'); + } + protected function newChallenge( PhabricatorAuthFactorConfig $config, PhabricatorUser $viewer) { diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -12,6 +12,12 @@ 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() { return pht( 'Attach a mobile authenticator application (like Authy '. diff --git a/src/applications/auth/phid/PhabricatorAuthAuthFactorProviderPHIDType.php b/src/applications/auth/phid/PhabricatorAuthAuthFactorProviderPHIDType.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/phid/PhabricatorAuthAuthFactorProviderPHIDType.php @@ -0,0 +1,40 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $provider = $objects[$phid]; + + $handle->setURI($provider->getURI()); + } + } + +} diff --git a/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php b/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/query/PhabricatorAuthFactorProviderQuery.php @@ -0,0 +1,67 @@ +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'; + } + +} diff --git a/src/applications/auth/query/PhabricatorAuthFactorProviderTransactionQuery.php b/src/applications/auth/query/PhabricatorAuthFactorProviderTransactionQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/query/PhabricatorAuthFactorProviderTransactionQuery.php @@ -0,0 +1,10 @@ +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; + } + + +} diff --git a/src/applications/auth/storage/PhabricatorAuthFactorProviderTransaction.php b/src/applications/auth/storage/PhabricatorAuthFactorProviderTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/storage/PhabricatorAuthFactorProviderTransaction.php @@ -0,0 +1,18 @@ +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(), + ); + } + +} diff --git a/src/applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php b/src/applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php @@ -0,0 +1,4 @@ +