PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/policy/storage/PhabricatorPolicy.php

http://github.com/facebook/phabricator
PHP | 515 lines | 375 code | 79 blank | 61 comment | 40 complexity | 474ec8f2c7d0a040dd684c272689212f MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. final class PhabricatorPolicy
  3. extends PhabricatorPolicyDAO
  4. implements
  5. PhabricatorPolicyInterface,
  6. PhabricatorDestructibleInterface {
  7. const ACTION_ALLOW = 'allow';
  8. const ACTION_DENY = 'deny';
  9. private $name;
  10. private $shortName;
  11. private $type;
  12. private $href;
  13. private $workflow;
  14. private $icon;
  15. protected $rules = array();
  16. protected $defaultAction = self::ACTION_DENY;
  17. private $ruleObjects = self::ATTACHABLE;
  18. protected function getConfiguration() {
  19. return array(
  20. self::CONFIG_AUX_PHID => true,
  21. self::CONFIG_SERIALIZATION => array(
  22. 'rules' => self::SERIALIZATION_JSON,
  23. ),
  24. self::CONFIG_COLUMN_SCHEMA => array(
  25. 'defaultAction' => 'text32',
  26. ),
  27. self::CONFIG_KEY_SCHEMA => array(
  28. 'key_phid' => null,
  29. 'phid' => array(
  30. 'columns' => array('phid'),
  31. 'unique' => true,
  32. ),
  33. ),
  34. ) + parent::getConfiguration();
  35. }
  36. public function generatePHID() {
  37. return PhabricatorPHID::generateNewPHID(
  38. PhabricatorPolicyPHIDTypePolicy::TYPECONST);
  39. }
  40. public static function newFromPolicyAndHandle(
  41. $policy_identifier,
  42. PhabricatorObjectHandle $handle = null) {
  43. $is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
  44. if ($is_global) {
  45. return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
  46. }
  47. $policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
  48. if ($policy) {
  49. return $policy;
  50. }
  51. if (!$handle) {
  52. throw new Exception(
  53. pht(
  54. "Policy identifier is an object PHID ('%s'), but no object handle ".
  55. "was provided. A handle must be provided for object policies.",
  56. $policy_identifier));
  57. }
  58. $handle_phid = $handle->getPHID();
  59. if ($policy_identifier != $handle_phid) {
  60. throw new Exception(
  61. pht(
  62. "Policy identifier is an object PHID ('%s'), but the provided ".
  63. "handle has a different PHID ('%s'). The handle must correspond ".
  64. "to the policy identifier.",
  65. $policy_identifier,
  66. $handle_phid));
  67. }
  68. $policy = id(new PhabricatorPolicy())
  69. ->setPHID($policy_identifier)
  70. ->setHref($handle->getURI());
  71. $phid_type = phid_get_type($policy_identifier);
  72. switch ($phid_type) {
  73. case PhabricatorProjectProjectPHIDType::TYPECONST:
  74. $policy
  75. ->setType(PhabricatorPolicyType::TYPE_PROJECT)
  76. ->setName($handle->getName())
  77. ->setIcon($handle->getIcon());
  78. break;
  79. case PhabricatorPeopleUserPHIDType::TYPECONST:
  80. $policy->setType(PhabricatorPolicyType::TYPE_USER);
  81. $policy->setName($handle->getFullName());
  82. break;
  83. case PhabricatorPolicyPHIDTypePolicy::TYPECONST:
  84. // TODO: This creates a weird handle-based version of a rule policy.
  85. // It behaves correctly, but can't be applied since it doesn't have
  86. // any rules. It is used to render transactions, and might need some
  87. // cleanup.
  88. break;
  89. default:
  90. $policy->setType(PhabricatorPolicyType::TYPE_MASKED);
  91. $policy->setName($handle->getFullName());
  92. break;
  93. }
  94. $policy->makeEphemeral();
  95. return $policy;
  96. }
  97. public function setType($type) {
  98. $this->type = $type;
  99. return $this;
  100. }
  101. public function getType() {
  102. if (!$this->type) {
  103. return PhabricatorPolicyType::TYPE_CUSTOM;
  104. }
  105. return $this->type;
  106. }
  107. public function setName($name) {
  108. $this->name = $name;
  109. return $this;
  110. }
  111. public function getName() {
  112. if (!$this->name) {
  113. return pht('Custom Policy');
  114. }
  115. return $this->name;
  116. }
  117. public function setShortName($short_name) {
  118. $this->shortName = $short_name;
  119. return $this;
  120. }
  121. public function getShortName() {
  122. if ($this->shortName) {
  123. return $this->shortName;
  124. }
  125. return $this->getName();
  126. }
  127. public function setHref($href) {
  128. $this->href = $href;
  129. return $this;
  130. }
  131. public function getHref() {
  132. return $this->href;
  133. }
  134. public function setWorkflow($workflow) {
  135. $this->workflow = $workflow;
  136. return $this;
  137. }
  138. public function getWorkflow() {
  139. return $this->workflow;
  140. }
  141. public function setIcon($icon) {
  142. $this->icon = $icon;
  143. return $this;
  144. }
  145. public function getIcon() {
  146. if ($this->icon) {
  147. return $this->icon;
  148. }
  149. switch ($this->getType()) {
  150. case PhabricatorPolicyType::TYPE_GLOBAL:
  151. static $map = array(
  152. PhabricatorPolicies::POLICY_PUBLIC => 'fa-globe',
  153. PhabricatorPolicies::POLICY_USER => 'fa-users',
  154. PhabricatorPolicies::POLICY_ADMIN => 'fa-eye',
  155. PhabricatorPolicies::POLICY_NOONE => 'fa-ban',
  156. );
  157. return idx($map, $this->getPHID(), 'fa-question-circle');
  158. case PhabricatorPolicyType::TYPE_USER:
  159. return 'fa-user';
  160. case PhabricatorPolicyType::TYPE_PROJECT:
  161. return 'fa-briefcase';
  162. case PhabricatorPolicyType::TYPE_CUSTOM:
  163. case PhabricatorPolicyType::TYPE_MASKED:
  164. return 'fa-certificate';
  165. default:
  166. return 'fa-question-circle';
  167. }
  168. }
  169. public function getSortKey() {
  170. return sprintf(
  171. '%02d%s',
  172. PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
  173. $this->getSortName());
  174. }
  175. private function getSortName() {
  176. if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
  177. static $map = array(
  178. PhabricatorPolicies::POLICY_PUBLIC => 0,
  179. PhabricatorPolicies::POLICY_USER => 1,
  180. PhabricatorPolicies::POLICY_ADMIN => 2,
  181. PhabricatorPolicies::POLICY_NOONE => 3,
  182. );
  183. return idx($map, $this->getPHID());
  184. }
  185. return $this->getName();
  186. }
  187. public static function getPolicyExplanation(
  188. PhabricatorUser $viewer,
  189. $policy) {
  190. $type = phid_get_type($policy);
  191. if ($type === PhabricatorProjectProjectPHIDType::TYPECONST) {
  192. $handle = id(new PhabricatorHandleQuery())
  193. ->setViewer($viewer)
  194. ->withPHIDs(array($policy))
  195. ->executeOne();
  196. return pht(
  197. 'Members of the project "%s" can take this action.',
  198. $handle->getFullName());
  199. }
  200. return self::getOpaquePolicyExplanation($viewer, $policy);
  201. }
  202. public static function getOpaquePolicyExplanation(
  203. PhabricatorUser $viewer,
  204. $policy) {
  205. $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
  206. if ($rule) {
  207. return $rule->getPolicyExplanation();
  208. }
  209. switch ($policy) {
  210. case PhabricatorPolicies::POLICY_PUBLIC:
  211. return pht(
  212. 'This object is public and can be viewed by anyone, even if they '.
  213. 'do not have a Phabricator account.');
  214. case PhabricatorPolicies::POLICY_USER:
  215. return pht('Logged in users can take this action.');
  216. case PhabricatorPolicies::POLICY_ADMIN:
  217. return pht('Administrators can take this action.');
  218. case PhabricatorPolicies::POLICY_NOONE:
  219. return pht('By default, no one can take this action.');
  220. default:
  221. $handle = id(new PhabricatorHandleQuery())
  222. ->setViewer($viewer)
  223. ->withPHIDs(array($policy))
  224. ->executeOne();
  225. $type = phid_get_type($policy);
  226. if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
  227. return pht(
  228. 'Members of a particular project can take this action. (You '.
  229. 'can not see this object, so the name of this project is '.
  230. 'restricted.)',
  231. $handle->getFullName());
  232. } else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
  233. return pht(
  234. '%s can take this action.',
  235. $handle->getFullName());
  236. } else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
  237. return pht(
  238. 'This object has a custom policy controlling who can take this '.
  239. 'action.');
  240. } else {
  241. return pht(
  242. 'This object has an unknown or invalid policy setting ("%s").',
  243. $policy);
  244. }
  245. }
  246. }
  247. public function getFullName() {
  248. switch ($this->getType()) {
  249. case PhabricatorPolicyType::TYPE_PROJECT:
  250. return pht('Members of Project: %s', $this->getName());
  251. case PhabricatorPolicyType::TYPE_MASKED:
  252. return pht('Other: %s', $this->getName());
  253. case PhabricatorPolicyType::TYPE_USER:
  254. return pht('Only User: %s', $this->getName());
  255. default:
  256. return $this->getName();
  257. }
  258. }
  259. public function newRef(PhabricatorUser $viewer) {
  260. return id(new PhabricatorPolicyRef())
  261. ->setViewer($viewer)
  262. ->setPolicy($this);
  263. }
  264. public function isProjectPolicy() {
  265. return ($this->getType() === PhabricatorPolicyType::TYPE_PROJECT);
  266. }
  267. public function isCustomPolicy() {
  268. return ($this->getType() === PhabricatorPolicyType::TYPE_CUSTOM);
  269. }
  270. public function isMaskedPolicy() {
  271. return ($this->getType() === PhabricatorPolicyType::TYPE_MASKED);
  272. }
  273. /**
  274. * Return a list of custom rule classes (concrete subclasses of
  275. * @{class:PhabricatorPolicyRule}) this policy uses.
  276. *
  277. * @return list<string> List of class names.
  278. */
  279. public function getCustomRuleClasses() {
  280. $classes = array();
  281. foreach ($this->getRules() as $rule) {
  282. if (!is_array($rule)) {
  283. // This rule is invalid. We'll reject it later, but don't need to
  284. // extract anything from it for now.
  285. continue;
  286. }
  287. $class = idx($rule, 'rule');
  288. try {
  289. if (class_exists($class)) {
  290. $classes[$class] = $class;
  291. }
  292. } catch (Exception $ex) {
  293. continue;
  294. }
  295. }
  296. return array_keys($classes);
  297. }
  298. /**
  299. * Return a list of all values used by a given rule class to implement this
  300. * policy. This is used to bulk load data (like project memberships) in order
  301. * to apply policy filters efficiently.
  302. *
  303. * @param string Policy rule classname.
  304. * @return list<wild> List of values used in this policy.
  305. */
  306. public function getCustomRuleValues($rule_class) {
  307. $values = array();
  308. foreach ($this->getRules() as $rule) {
  309. if ($rule['rule'] == $rule_class) {
  310. $values[] = $rule['value'];
  311. }
  312. }
  313. return $values;
  314. }
  315. public function attachRuleObjects(array $objects) {
  316. $this->ruleObjects = $objects;
  317. return $this;
  318. }
  319. public function getRuleObjects() {
  320. return $this->assertAttached($this->ruleObjects);
  321. }
  322. /**
  323. * Return `true` if this policy is stronger (more restrictive) than some
  324. * other policy.
  325. *
  326. * Because policies are complicated, determining which policies are
  327. * "stronger" is not trivial. This method uses a very coarse working
  328. * definition of policy strength which is cheap to compute, unambiguous,
  329. * and intuitive in the common cases.
  330. *
  331. * This method returns `true` if the //class// of this policy is stronger
  332. * than the other policy, even if the policies are (or might be) the same in
  333. * practice. For example, "Members of Project X" is considered a stronger
  334. * policy than "All Users", even though "Project X" might (in some rare
  335. * cases) contain every user.
  336. *
  337. * Generally, the ordering here is:
  338. *
  339. * - Public
  340. * - All Users
  341. * - (Everything Else)
  342. * - No One
  343. *
  344. * In the "everything else" bucket, we can't make any broad claims about
  345. * which policy is stronger (and we especially can't make those claims
  346. * cheaply).
  347. *
  348. * Even if we fully evaluated each policy, the two policies might be
  349. * "Members of X" and "Members of Y", each of which permits access to some
  350. * set of unique users. In this case, neither is strictly stronger than
  351. * the other.
  352. *
  353. * @param PhabricatorPolicy Other policy.
  354. * @return bool `true` if this policy is more restrictive than the other
  355. * policy.
  356. */
  357. public function isStrongerThan(PhabricatorPolicy $other) {
  358. $this_policy = $this->getPHID();
  359. $other_policy = $other->getPHID();
  360. $strengths = array(
  361. PhabricatorPolicies::POLICY_PUBLIC => -2,
  362. PhabricatorPolicies::POLICY_USER => -1,
  363. // (Default policies have strength 0.)
  364. PhabricatorPolicies::POLICY_NOONE => 1,
  365. );
  366. $this_strength = idx($strengths, $this->getPHID(), 0);
  367. $other_strength = idx($strengths, $other->getPHID(), 0);
  368. return ($this_strength > $other_strength);
  369. }
  370. public function isValidPolicyForEdit() {
  371. return $this->getType() !== PhabricatorPolicyType::TYPE_MASKED;
  372. }
  373. public static function getSpecialRules(
  374. PhabricatorPolicyInterface $object,
  375. PhabricatorUser $viewer,
  376. $capability,
  377. $active_only) {
  378. $exceptions = array();
  379. if ($object instanceof PhabricatorPolicyCodexInterface) {
  380. $codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer))
  381. ->setCapability($capability);
  382. $rules = $codex->getPolicySpecialRuleDescriptions();
  383. foreach ($rules as $rule) {
  384. $is_active = $rule->getIsActive();
  385. if ($is_active) {
  386. $rule_capabilities = $rule->getCapabilities();
  387. if ($rule_capabilities) {
  388. if (!in_array($capability, $rule_capabilities)) {
  389. $is_active = false;
  390. }
  391. }
  392. }
  393. if (!$is_active && $active_only) {
  394. continue;
  395. }
  396. $description = $rule->getDescription();
  397. if (!$is_active) {
  398. $description = phutil_tag(
  399. 'span',
  400. array(
  401. 'class' => 'phui-policy-section-view-inactive-rule',
  402. ),
  403. $description);
  404. }
  405. $exceptions[] = $description;
  406. }
  407. }
  408. if (!$exceptions) {
  409. if (method_exists($object, 'describeAutomaticCapability')) {
  410. $exceptions = (array)$object->describeAutomaticCapability($capability);
  411. $exceptions = array_filter($exceptions);
  412. }
  413. }
  414. return $exceptions;
  415. }
  416. /* -( PhabricatorPolicyInterface )----------------------------------------- */
  417. public function getCapabilities() {
  418. return array(
  419. PhabricatorPolicyCapability::CAN_VIEW,
  420. );
  421. }
  422. public function getPolicy($capability) {
  423. // NOTE: We implement policies only so we can comply with the interface.
  424. // The actual query skips them, as enforcing policies on policies seems
  425. // perilous and isn't currently required by the application.
  426. return PhabricatorPolicies::POLICY_PUBLIC;
  427. }
  428. public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
  429. return false;
  430. }
  431. /* -( PhabricatorDestructibleInterface )----------------------------------- */
  432. public function destroyObjectPermanently(
  433. PhabricatorDestructionEngine $engine) {
  434. $this->delete();
  435. }
  436. }