From c830461b00a03fe5c6f4496f8bab99172c3aa688 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 3 Oct 2013 12:40:08 -0700 Subject: [PATCH] Allow application policies to be edited Summary: Ref T603. Enables: - Application policies can be edited. - Applications can define custom policies (this will be used for setting defaults, like "what is the default visibiltiy of new tasks", and meta-policies, like "who can create a task?"). Test Plan: Edited application policies. A future diff does more with custom policies. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T603 Differential Revision: https://secure.phabricator.com/D7205 --- src/__phutil_library_map__.php | 2 + .../base/PhabricatorApplication.php | 100 +++++++++++- .../PhabricatorApplicationApplications.php | 2 + ...ricatorApplicationDetailViewController.php | 77 ++++++--- .../PhabricatorApplicationEditController.php | 151 ++++++++++++++++++ 5 files changed, 305 insertions(+), 27 deletions(-) create mode 100644 src/applications/meta/controller/PhabricatorApplicationEditController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 75a8571960..e76fed12a0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -828,6 +828,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationDiviner' => 'applications/diviner/application/PhabricatorApplicationDiviner.php', 'PhabricatorApplicationDoorkeeper' => 'applications/doorkeeper/application/PhabricatorApplicationDoorkeeper.php', 'PhabricatorApplicationDrydock' => 'applications/drydock/application/PhabricatorApplicationDrydock.php', + 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 'PhabricatorApplicationFact' => 'applications/fact/application/PhabricatorApplicationFact.php', 'PhabricatorApplicationFeed' => 'applications/feed/application/PhabricatorApplicationFeed.php', 'PhabricatorApplicationFiles' => 'applications/files/application/PhabricatorApplicationFiles.php', @@ -2937,6 +2938,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationDiviner' => 'PhabricatorApplication', 'PhabricatorApplicationDoorkeeper' => 'PhabricatorApplication', 'PhabricatorApplicationDrydock' => 'PhabricatorApplication', + 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationFact' => 'PhabricatorApplication', 'PhabricatorApplicationFeed' => 'PhabricatorApplication', 'PhabricatorApplicationFiles' => 'PhabricatorApplication', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 8949893018..4574a7bb10 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -321,18 +321,32 @@ abstract class PhabricatorApplication public function getCapabilities() { - return array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - ); + return array_merge( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ), + array_keys($this->getCustomCapabilities())); } public function getPolicy($capability) { + $default = $this->getCustomPolicySetting($capability); + if ($default) { + return $default; + } + switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - return PhabricatorPolicies::POLICY_USER; + if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { + return PhabricatorPolicies::POLICY_PUBLIC; + } else { + return PhabricatorPolicies::POLICY_USER; + } case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; + default: + $spec = $this->getCustomCapabilitySpecification($capability); + return idx($spec, 'default', PhabricatorPolicies::POLICY_USER); } } @@ -345,4 +359,80 @@ abstract class PhabricatorApplication } +/* -( Policies )----------------------------------------------------------- */ + + protected function getCustomCapabilities() { + return array(); + } + + private function getCustomPolicySetting($capability) { + if (!$this->isCapabilityEditable($capability)) { + return null; + } + + $config = PhabricatorEnv::getEnvConfig('phabricator.application-settings'); + + $app = idx($config, $this->getPHID()); + if (!$app) { + return null; + } + + $policy = idx($app, 'policy'); + if (!$policy) { + return null; + } + + return idx($policy, $capability); + } + + + private function getCustomCapabilitySpecification($capability) { + $custom = $this->getCustomCapabilities(); + if (empty($custom[$capability])) { + throw new Exception("Unknown capability '{$capability}'!"); + } + return $custom[$capability]; + } + + public function getCapabilityLabel($capability) { + $map = array( + PhabricatorPolicyCapability::CAN_VIEW => pht('Can Use Application'), + PhabricatorPolicyCapability::CAN_EDIT => pht('Can Configure Application'), + ); + + $map += ipull($this->getCustomCapabilities(), 'label'); + + return idx($map, $capability); + } + + public function isCapabilityEditable($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->canUninstall(); + case PhabricatorPolicyCapability::CAN_EDIT: + return false; + default: + $spec = $this->getCustomCapabilitySpecification($capability); + return idx($spec, 'edit', true); + } + } + + public function getCapabilityCaption($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + if (!$this->canUninstall()) { + return pht( + 'This application is required for Phabricator to operate, so all '. + 'users must have access to it.'); + } else { + return null; + } + case PhabricatorPolicyCapability::CAN_EDIT: + return null; + default: + $spec = $this->getCustomCapabilitySpecification($capability); + return idx($spec, 'caption'); + } + } + } diff --git a/src/applications/meta/application/PhabricatorApplicationApplications.php b/src/applications/meta/application/PhabricatorApplicationApplications.php index 314f878d13..765e5e0b0d 100644 --- a/src/applications/meta/application/PhabricatorApplicationApplications.php +++ b/src/applications/meta/application/PhabricatorApplicationApplications.php @@ -33,6 +33,8 @@ final class PhabricatorApplicationApplications extends PhabricatorApplication { 'PhabricatorApplicationsListController', 'view/(?P\w+)/' => 'PhabricatorApplicationDetailViewController', + 'edit/(?P\w+)/' => + 'PhabricatorApplicationEditController', '(?P\w+)/(?Pinstall|uninstall)/' => 'PhabricatorApplicationUninstallController', ), diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index c789aaa969..6b6e3a26fa 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -13,8 +13,10 @@ final class PhabricatorApplicationDetailViewController $request = $this->getRequest(); $user = $request->getUser(); - $selected = PhabricatorApplication::getByClass($this->application); - + $selected = id(new PhabricatorApplicationQuery()) + ->setViewer($user) + ->withClasses(array($this->application)) + ->executeOne(); if (!$selected) { return new Aphront404Response(); } @@ -24,8 +26,7 @@ final class PhabricatorApplicationDetailViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) - ->setName(pht('Applications')) - ->setHref($this->getApplicationURI())); + ->setName($selected->getName())); $header = id(new PHUIHeaderView()) ->setHeader($title); @@ -70,37 +71,68 @@ final class PhabricatorApplicationDetailViewController )); } - private function buildPropertyView(PhabricatorApplication $selected) { + private function buildPropertyView(PhabricatorApplication $application) { + $viewer = $this->getRequest()->getUser(); + $properties = id(new PhabricatorPropertyListView()) - ->addProperty( - pht('Description'), $selected->getShortDescription()); + ->addProperty(pht('Description'), $application->getShortDescription()); + + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $viewer, + $application); + + $properties->addSectionHeader(pht('Policies')); + + foreach ($application->getCapabilities() as $capability) { + $properties->addProperty( + $application->getCapabilityLabel($capability), + idx($descriptions, $capability)); + } return $properties; } private function buildActionView( - PhabricatorUser $user, PhabricatorApplication $selected) { + PhabricatorUser $user, + PhabricatorApplication $selected) { $view = id(new PhabricatorActionListView()) ->setUser($user) ->setObjectURI($this->getRequest()->getRequestURI()); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $user, + $selected, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = $this->getApplicationURI('edit/'.get_class($selected).'/'); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Policies')) + ->setIcon('edit') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); + if ($selected->canUninstall()) { if ($selected->isInstalled()) { $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Uninstall')) - ->setIcon('delete') - ->setWorkflow(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/uninstall/'))); + id(new PhabricatorActionView()) + ->setName(pht('Uninstall')) + ->setIcon('delete') + ->setDisabled(!$can_edit) + ->setWorkflow(true) + ->setHref( + $this->getApplicationURI(get_class($selected).'/uninstall/'))); } else { $action = id(new PhabricatorActionView()) ->setName(pht('Install')) ->setIcon('new') + ->setDisabled(!$can_edit) ->setWorkflow(true) ->setHref( - $this->getApplicationURI(get_class($selected).'/install/')); + $this->getApplicationURI(get_class($selected).'/install/')); $beta_enabled = PhabricatorEnv::getEnvConfig( 'phabricator.show-beta-applications'); @@ -112,14 +144,15 @@ final class PhabricatorApplicationDetailViewController } } else { $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Uninstall')) - ->setIcon('delete') - ->setWorkflow(true) - ->setDisabled(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/uninstall/'))); + id(new PhabricatorActionView()) + ->setName(pht('Uninstall')) + ->setIcon('delete') + ->setWorkflow(true) + ->setDisabled(true) + ->setHref( + $this->getApplicationURI(get_class($selected).'/uninstall/'))); } + return $view; } diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php new file mode 100644 index 0000000000..9c01268891 --- /dev/null +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -0,0 +1,151 @@ +application = $data['application']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $application = id(new PhabricatorApplicationQuery()) + ->setViewer($user) + ->withClasses(array($this->application)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$application) { + return new Aphront404Response(); + } + + $title = $application->getName(); + + $view_uri = $this->getApplicationURI('view/'.get_class($application).'/'); + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($user) + ->setObject($application) + ->execute(); + + if ($request->isFormPost()) { + $result = array(); + foreach ($application->getCapabilities() as $capability) { + $old = $application->getPolicy($capability); + $new = $request->getStr('policy:'.$capability); + + if ($old == $new) { + // No change to the setting. + continue; + } + + if (empty($policies[$new])) { + // Can't set the policy to something invalid. + continue; + } + + if ($new == PhabricatorPolicies::POLICY_PUBLIC && + $capability != PhabricatorPolicyCapability::CAN_VIEW) { + // Can't set policies other than "view" to public. + continue; + } + + $result[$capability] = $new; + } + + if ($result) { + $key = 'phabricator.application-settings'; + $config_entry = PhabricatorConfigEntry::loadConfigEntry($key); + $value = $config_entry->getValue(); + + $phid = $application->getPHID(); + if (empty($value[$phid])) { + $value[$application->getPHID()] = array(); + } + if (empty($value[$phid]['policy'])) { + $value[$phid]['policy'] = array(); + } + + $value[$phid]['policy'] = $result + $value[$phid]['policy']; + + PhabricatorConfigEditor::storeNewValue( + $config_entry, + $value, + $this->getRequest()); + } + + return id(new AphrontRedirectResponse())->setURI($view_uri); + } + + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( + $user, + $application); + + $form = id(new AphrontFormView()) + ->setUser($user); + + foreach ($application->getCapabilities() as $capability) { + $label = $application->getCapabilityLabel($capability); + $can_edit = $application->isCapabilityEditable($capability); + $caption = $application->getCapabilityCaption($capability); + + if (!$can_edit) { + $form->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel($label) + ->setValue(idx($descriptions, $capability)) + ->setCaption($caption)); + } else { + $form->appendChild( + id(new AphrontFormPolicyControl()) + ->setUser($user) + ->setCapability($capability) + ->setPolicyObject($application) + ->setPolicies($policies) + ->setLabel($label) + ->setName('policy:'.$capability) + ->setCaption($caption)); + } + + } + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save Policies')) + ->addCancelButton($view_uri)); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName($application->getName()) + ->setHref($view_uri)); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit Policies'))); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Policies: %s', $application->getName())); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setForm($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $object_box, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + +}