diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php index 307cab61c8..0b3487b71b 100644 --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -1,154 +1,154 @@ getIsAdmin(); } public function getName() { return pht('Auth'); } public function getShortDescription() { return pht('Login/Registration'); } public function getHelpDocumentationArticles(PhabricatorUser $viewer) { // NOTE: Although reasonable help exists for this in "Configuring Accounts // and Registration", specifying help items here means we get the menu // item in all the login/link interfaces, which is confusing and not // helpful. // TODO: Special case this, or split the auth and auth administration // applications? return array(); } public function getApplicationGroup() { return self::GROUP_ADMIN; } public function getRoutes() { return array( '/auth/' => array( '' => 'PhabricatorAuthListController', 'config/' => array( 'new/' => 'PhabricatorAuthNewController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorAuthEditController', '(?Penable|disable)/(?P\d+)/' => 'PhabricatorAuthDisableController', 'view/(?P\d+)/' => 'PhabricatorAuthProviderViewController', ), 'login/(?P[^/]+)/(?:(?P[^/]+)/)?' => 'PhabricatorAuthLoginController', '(?Ploggedout)/' => 'PhabricatorAuthStartController', 'invite/(?P[^/]+)/' => 'PhabricatorAuthInviteController', 'register/(?:(?P[^/]+)/)?' => 'PhabricatorAuthRegisterController', 'start/' => 'PhabricatorAuthStartController', 'validate/' => 'PhabricatorAuthValidateController', 'finish/' => 'PhabricatorAuthFinishController', - 'unlink/(?P[^/]+)/' => 'PhabricatorAuthUnlinkController', + 'unlink/(?P\d+)/' => 'PhabricatorAuthUnlinkController', '(?Plink|refresh)/(?P[^/]+)/' => 'PhabricatorAuthLinkController', 'confirmlink/(?P[^/]+)/' => 'PhabricatorAuthConfirmLinkController', 'session/terminate/(?P[^/]+)/' => 'PhabricatorAuthTerminateSessionController', 'token/revoke/(?P[^/]+)/' => 'PhabricatorAuthRevokeTokenController', 'session/downgrade/' => 'PhabricatorAuthDowngradeSessionController', 'enroll/' => array( '(?:(?P[^/]+)/)?(?:(?Psaved)/)?' => 'PhabricatorAuthNeedsMultiFactorController', ), 'sshkey/' => array( $this->getQueryRoutePattern('for/(?P[^/]+)/') => 'PhabricatorAuthSSHKeyListController', 'generate/' => 'PhabricatorAuthSSHKeyGenerateController', 'upload/' => 'PhabricatorAuthSSHKeyEditController', 'edit/(?P\d+)/' => 'PhabricatorAuthSSHKeyEditController', 'revoke/(?P\d+)/' => 'PhabricatorAuthSSHKeyRevokeController', 'view/(?P\d+)/' => 'PhabricatorAuthSSHKeyViewController', ), 'password/' => 'PhabricatorAuthSetPasswordController', 'mfa/' => array( $this->getQueryRoutePattern() => 'PhabricatorAuthFactorProviderListController', $this->getEditRoutePattern('edit/') => 'PhabricatorAuthFactorProviderEditController', '(?P[1-9]\d*)/' => 'PhabricatorAuthFactorProviderViewController', 'message/(?P[1-9]\d*)/' => 'PhabricatorAuthFactorProviderMessageController', ), 'message/' => array( $this->getQueryRoutePattern() => 'PhabricatorAuthMessageListController', $this->getEditRoutePattern('edit/') => 'PhabricatorAuthMessageEditController', '(?P[1-9]\d*)/' => 'PhabricatorAuthMessageViewController', ), 'contact/' => array( $this->getEditRoutePattern('edit/') => 'PhabricatorAuthContactNumberEditController', '(?P[1-9]\d*)/' => 'PhabricatorAuthContactNumberViewController', '(?Pdisable|enable)/(?P[1-9]\d*)/' => 'PhabricatorAuthContactNumberDisableController', 'primary/(?P[1-9]\d*)/' => 'PhabricatorAuthContactNumberPrimaryController', 'test/(?P[1-9]\d*)/' => 'PhabricatorAuthContactNumberTestController', ), ), '/oauth/(?P\w+)/login/' => 'PhabricatorAuthOldOAuthRedirectController', '/login/' => array( '' => 'PhabricatorAuthStartController', 'email/' => 'PhabricatorEmailLoginController', 'once/'. '(?P[^/]+)/'. '(?P\d+)/'. '(?P[^/]+)/'. '(?:(?P\d+)/)?' => 'PhabricatorAuthOneTimeLoginController', 'refresh/' => 'PhabricatorRefreshCSRFController', 'mustverify/' => 'PhabricatorMustVerifyEmailController', ), '/emailverify/(?P[^/]+)/' => 'PhabricatorEmailVerificationController', '/logout/' => 'PhabricatorLogoutController', ); } protected function getCustomCapabilities() { return array( AuthManageProvidersCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php index 1e3023e5d2..004ddf4f9a 100644 --- a/src/applications/auth/controller/PhabricatorAuthUnlinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthUnlinkController.php @@ -1,154 +1,133 @@ getViewer(); - $this->providerKey = $request->getURIData('pkey'); - - list($type, $domain) = explode(':', $this->providerKey, 2); - - // Check that this account link actually exists. We don't require the - // provider to exist because we want users to be able to delete links to - // dead accounts if they want. - $account = id(new PhabricatorExternalAccount())->loadOneWhere( - 'accountType = %s AND accountDomain = %s AND userPHID = %s', - $type, - $domain, - $viewer->getPHID()); + $id = $request->getURIData('id'); + + $account = id(new PhabricatorExternalAccountQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); if (!$account) { - return $this->renderNoAccountErrorDialog(); + return new Aphront404Response(); } - // Check that the provider (if it exists) allows accounts to be unlinked. - $provider_key = $this->providerKey; - $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); - if ($provider) { - if (!$provider->shouldAllowAccountUnlink()) { - return $this->renderNotUnlinkableErrorDialog($provider); - } + $done_uri = '/settings/panel/external/'; + + $config = $account->getProviderConfig(); + $provider = $config->getProvider(); + if (!$provider->shouldAllowAccountUnlink()) { + return $this->renderNotUnlinkableErrorDialog($provider, $done_uri); } $confirmations = $request->getStrList('confirmations'); $confirmations = array_fuse($confirmations); if (!$request->isFormPost() || !isset($confirmations['unlink'])) { - return $this->renderConfirmDialog($confirmations); + return $this->renderConfirmDialog($confirmations, $config, $done_uri); } // Check that this account isn't the only account which can be used to // login. We warn you when you remove your only login account. if ($account->isUsableForLogin()) { - $other_accounts = id(new PhabricatorExternalAccount())->loadAllWhere( - 'userPHID = %s', - $viewer->getPHID()); + $other_accounts = id(new PhabricatorExternalAccountQuery()) + ->setViewer($viewer) + ->withUserPHIDs(array($viewer->getPHID())) + ->execute(); $valid_accounts = 0; foreach ($other_accounts as $other_account) { if ($other_account->isUsableForLogin()) { $valid_accounts++; } } if ($valid_accounts < 2) { if (!isset($confirmations['only'])) { - return $this->renderOnlyUsableAccountConfirmDialog($confirmations); + return $this->renderOnlyUsableAccountConfirmDialog( + $confirmations, + $done_uri); } } } $account->delete(); id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( $viewer, new PhutilOpaqueEnvelope( $request->getCookie(PhabricatorCookies::COOKIE_SESSION))); - return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); - } - - private function getDoneURI() { - return '/settings/panel/external/'; - } - - private function renderNoAccountErrorDialog() { - $dialog = id(new AphrontDialogView()) - ->setUser($this->getRequest()->getUser()) - ->setTitle(pht('No Such Account')) - ->appendChild( - pht( - 'You can not unlink this account because it is not linked.')) - ->addCancelButton($this->getDoneURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); + return id(new AphrontRedirectResponse())->setURI($done_uri); } private function renderNotUnlinkableErrorDialog( - PhabricatorAuthProvider $provider) { + PhabricatorAuthProvider $provider, + $done_uri) { - $dialog = id(new AphrontDialogView()) - ->setUser($this->getRequest()->getUser()) + return $this->newDialog() ->setTitle(pht('Permanent Account Link')) ->appendChild( pht( 'You can not unlink this account because the administrator has '. - 'configured Phabricator to make links to %s accounts permanent.', + 'configured Phabricator to make links to "%s" accounts permanent.', $provider->getProviderName())) - ->addCancelButton($this->getDoneURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); + ->addCancelButton($done_uri); } - private function renderOnlyUsableAccountConfirmDialog(array $confirmations) { + private function renderOnlyUsableAccountConfirmDialog( + array $confirmations, + $done_uri) { + $confirmations[] = 'only'; return $this->newDialog() ->setTitle(pht('Unlink Your Only Login Account?')) ->addHiddenInput('confirmations', implode(',', $confirmations)) ->appendParagraph( pht( 'This is the only external login account linked to your Phabicator '. 'account. If you remove it, you may no longer be able to log in.')) ->appendParagraph( pht( 'If you lose access to your account, you can recover access by '. 'sending yourself an email login link from the login screen.')) - ->addCancelButton($this->getDoneURI()) + ->addCancelButton($done_uri) ->addSubmitButton(pht('Unlink External Account')); } - private function renderConfirmDialog(array $confirmations) { + private function renderConfirmDialog( + array $confirmations, + PhabricatorAuthProviderConfig $config, + $done_uri) { + $confirmations[] = 'unlink'; + $provider = $config->getProvider(); - $provider_key = $this->providerKey; - $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); - - if ($provider) { - $title = pht('Unlink "%s" Account?', $provider->getProviderName()); - $body = pht( - 'You will no longer be able to use your %s account to '. - 'log in to Phabricator.', - $provider->getProviderName()); - } else { - $title = pht('Unlink Account?'); - $body = pht( - 'You will no longer be able to use this account to log in '. - 'to Phabricator.'); - } + $title = pht('Unlink "%s" Account?', $provider->getProviderName()); + $body = pht( + 'You will no longer be able to use your %s account to '. + 'log in to Phabricator.', + $provider->getProviderName()); return $this->newDialog() ->setTitle($title) ->addHiddenInput('confirmations', implode(',', $confirmations)) ->appendParagraph($body) ->appendParagraph( pht( 'Note: Unlinking an authentication provider will terminate any '. 'other active login sessions.')) ->addSubmitButton(pht('Unlink Account')) - ->addCancelButton($this->getDoneURI()); + ->addCancelButton($done_uri); } } diff --git a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php index 29ef9fa2c7..9904c7369f 100644 --- a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php @@ -1,145 +1,145 @@ getUser(); $providers = PhabricatorAuthProvider::getAllProviders(); $accounts = id(new PhabricatorExternalAccountQuery()) ->setViewer($viewer) ->withUserPHIDs(array($viewer->getPHID())) ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); $linked_head = pht('Linked Accounts and Authentication'); $linked = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setNoDataString(pht('You have no linked accounts.')); foreach ($accounts as $account) { $item = new PHUIObjectItemView(); $provider = idx($providers, $account->getProviderKey()); if ($provider) { $item->setHeader($provider->getProviderName()); $can_unlink = $provider->shouldAllowAccountUnlink(); if (!$can_unlink) { $item->addAttribute(pht('Permanently Linked')); } } else { $item->setHeader( pht('Unknown Account ("%s")', $account->getProviderKey())); $can_unlink = true; } $can_login = $account->isUsableForLogin(); if (!$can_login) { $item->addAttribute( pht( 'Disabled (an administrator has disabled login for this '. 'account provider).')); } $can_refresh = $provider && $provider->shouldAllowAccountRefresh(); if ($can_refresh) { $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-refresh') ->setHref('/auth/refresh/'.$account->getProviderKey().'/')); } $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(!$can_unlink) - ->setHref('/auth/unlink/'.$account->getProviderKey().'/')); + ->setHref('/auth/unlink/'.$account->getID().'/')); if ($provider) { $provider->willRenderLinkedAccount($viewer, $item, $account); } $linked->addItem($item); } $linkable_head = pht('Add External Account'); $linkable = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setNoDataString( pht('Your account is linked with all available providers.')); $accounts = mpull($accounts, null, 'getProviderKey'); $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) ->withIsEnabled(true) ->execute(); $configs = msort($configs, 'getSortVector'); foreach ($configs as $config) { $provider = $config->getProvider(); if (!$provider->shouldAllowAccountLink()) { continue; } // Don't show the user providers they already have linked. $provider_key = $config->getProvider()->getProviderKey(); if (isset($accounts[$provider_key])) { continue; } $link_uri = '/auth/link/'.$provider->getProviderKey().'/'; $link_button = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-link') ->setHref($link_uri) ->setColor(PHUIButtonView::GREY) ->setText(pht('Link External Account')); $item = id(new PHUIObjectItemView()) ->setHeader($config->getDisplayName()) ->setHref($link_uri) ->setImageIcon($config->newIconView()) ->setSideColumn($link_button); $linkable->addItem($item); } $linked_box = $this->newBox($linked_head, $linked); $linkable_box = $this->newBox($linkable_head, $linkable); return array( $linked_box, $linkable_box, ); } }