Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / policy / storage / PhabricatorPolicy.php
blob7904f1792754cda1ccc57c137a9a83d89c6ab008
1 <?php
3 final class PhabricatorPolicy
4 extends PhabricatorPolicyDAO
5 implements
6 PhabricatorPolicyInterface,
7 PhabricatorDestructibleInterface {
9 const ACTION_ALLOW = 'allow';
10 const ACTION_DENY = 'deny';
12 private $name;
13 private $shortName;
14 private $type;
15 private $href;
16 private $workflow;
17 private $icon;
19 protected $rules = array();
20 protected $defaultAction = self::ACTION_DENY;
22 private $ruleObjects = self::ATTACHABLE;
24 protected function getConfiguration() {
25 return array(
26 self::CONFIG_AUX_PHID => true,
27 self::CONFIG_SERIALIZATION => array(
28 'rules' => self::SERIALIZATION_JSON,
30 self::CONFIG_COLUMN_SCHEMA => array(
31 'defaultAction' => 'text32',
33 self::CONFIG_KEY_SCHEMA => array(
34 'key_phid' => null,
35 'phid' => array(
36 'columns' => array('phid'),
37 'unique' => true,
40 ) + parent::getConfiguration();
43 public function generatePHID() {
44 return PhabricatorPHID::generateNewPHID(
45 PhabricatorPolicyPHIDTypePolicy::TYPECONST);
48 public static function newFromPolicyAndHandle(
49 $policy_identifier,
50 PhabricatorObjectHandle $handle = null) {
52 $is_global = PhabricatorPolicyQuery::isGlobalPolicy($policy_identifier);
53 if ($is_global) {
54 return PhabricatorPolicyQuery::getGlobalPolicy($policy_identifier);
57 $policy = PhabricatorPolicyQuery::getObjectPolicy($policy_identifier);
58 if ($policy) {
59 return $policy;
62 if (!$handle) {
63 throw new Exception(
64 pht(
65 "Policy identifier is an object PHID ('%s'), but no object handle ".
66 "was provided. A handle must be provided for object policies.",
67 $policy_identifier));
70 $handle_phid = $handle->getPHID();
71 if ($policy_identifier != $handle_phid) {
72 throw new Exception(
73 pht(
74 "Policy identifier is an object PHID ('%s'), but the provided ".
75 "handle has a different PHID ('%s'). The handle must correspond ".
76 "to the policy identifier.",
77 $policy_identifier,
78 $handle_phid));
81 $policy = id(new PhabricatorPolicy())
82 ->setPHID($policy_identifier)
83 ->setHref($handle->getURI());
85 $phid_type = phid_get_type($policy_identifier);
86 switch ($phid_type) {
87 case PhabricatorProjectProjectPHIDType::TYPECONST:
88 $policy
89 ->setType(PhabricatorPolicyType::TYPE_PROJECT)
90 ->setName($handle->getName())
91 ->setIcon($handle->getIcon());
92 break;
93 case PhabricatorPeopleUserPHIDType::TYPECONST:
94 $policy->setType(PhabricatorPolicyType::TYPE_USER);
95 $policy->setName($handle->getFullName());
96 break;
97 case PhabricatorPolicyPHIDTypePolicy::TYPECONST:
98 // TODO: This creates a weird handle-based version of a rule policy.
99 // It behaves correctly, but can't be applied since it doesn't have
100 // any rules. It is used to render transactions, and might need some
101 // cleanup.
102 break;
103 default:
104 $policy->setType(PhabricatorPolicyType::TYPE_MASKED);
105 $policy->setName($handle->getFullName());
106 break;
109 $policy->makeEphemeral();
111 return $policy;
114 public function setType($type) {
115 $this->type = $type;
116 return $this;
119 public function getType() {
120 if (!$this->type) {
121 return PhabricatorPolicyType::TYPE_CUSTOM;
123 return $this->type;
126 public function setName($name) {
127 $this->name = $name;
128 return $this;
131 public function getName() {
132 if (!$this->name) {
133 return pht('Custom Policy');
135 return $this->name;
138 public function setShortName($short_name) {
139 $this->shortName = $short_name;
140 return $this;
143 public function getShortName() {
144 if ($this->shortName) {
145 return $this->shortName;
147 return $this->getName();
150 public function setHref($href) {
151 $this->href = $href;
152 return $this;
155 public function getHref() {
156 return $this->href;
159 public function setWorkflow($workflow) {
160 $this->workflow = $workflow;
161 return $this;
164 public function getWorkflow() {
165 return $this->workflow;
168 public function setIcon($icon) {
169 $this->icon = $icon;
170 return $this;
173 public function getIcon() {
174 if ($this->icon) {
175 return $this->icon;
178 switch ($this->getType()) {
179 case PhabricatorPolicyType::TYPE_GLOBAL:
180 static $map = array(
181 PhabricatorPolicies::POLICY_PUBLIC => 'fa-globe',
182 PhabricatorPolicies::POLICY_USER => 'fa-users',
183 PhabricatorPolicies::POLICY_ADMIN => 'fa-eye',
184 PhabricatorPolicies::POLICY_NOONE => 'fa-ban',
186 return idx($map, $this->getPHID(), 'fa-question-circle');
187 case PhabricatorPolicyType::TYPE_USER:
188 return 'fa-user';
189 case PhabricatorPolicyType::TYPE_PROJECT:
190 return 'fa-briefcase';
191 case PhabricatorPolicyType::TYPE_CUSTOM:
192 case PhabricatorPolicyType::TYPE_MASKED:
193 return 'fa-certificate';
194 default:
195 return 'fa-question-circle';
199 public function getSortKey() {
200 return sprintf(
201 '%02d%s',
202 PhabricatorPolicyType::getPolicyTypeOrder($this->getType()),
203 $this->getSortName());
206 private function getSortName() {
207 if ($this->getType() == PhabricatorPolicyType::TYPE_GLOBAL) {
208 static $map = array(
209 PhabricatorPolicies::POLICY_PUBLIC => 0,
210 PhabricatorPolicies::POLICY_USER => 1,
211 PhabricatorPolicies::POLICY_ADMIN => 2,
212 PhabricatorPolicies::POLICY_NOONE => 3,
214 return idx($map, $this->getPHID());
216 return $this->getName();
219 public static function getPolicyExplanation(
220 PhabricatorUser $viewer,
221 $policy) {
223 $type = phid_get_type($policy);
224 if ($type === PhabricatorProjectProjectPHIDType::TYPECONST) {
225 $handle = id(new PhabricatorHandleQuery())
226 ->setViewer($viewer)
227 ->withPHIDs(array($policy))
228 ->executeOne();
230 return pht(
231 'Members of the project "%s" can take this action.',
232 $handle->getFullName());
235 return self::getOpaquePolicyExplanation($viewer, $policy);
238 public static function getOpaquePolicyExplanation(
239 PhabricatorUser $viewer,
240 $policy) {
242 $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy);
243 if ($rule) {
244 return $rule->getPolicyExplanation();
247 switch ($policy) {
248 case PhabricatorPolicies::POLICY_PUBLIC:
249 return pht(
250 'This object is public and can be viewed by anyone, even if they '.
251 'do not have a Phabricator account.');
252 case PhabricatorPolicies::POLICY_USER:
253 return pht('Logged in users can take this action.');
254 case PhabricatorPolicies::POLICY_ADMIN:
255 return pht('Administrators can take this action.');
256 case PhabricatorPolicies::POLICY_NOONE:
257 return pht('By default, no one can take this action.');
258 default:
259 $handle = id(new PhabricatorHandleQuery())
260 ->setViewer($viewer)
261 ->withPHIDs(array($policy))
262 ->executeOne();
264 $type = phid_get_type($policy);
265 if ($type == PhabricatorProjectProjectPHIDType::TYPECONST) {
266 return pht(
267 'Members of a particular project can take this action. (You '.
268 'can not see this object, so the name of this project is '.
269 'restricted.)');
270 } else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
271 return pht(
272 '%s can take this action.',
273 $handle->getFullName());
274 } else if ($type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
275 return pht(
276 'This object has a custom policy controlling who can take this '.
277 'action.');
278 } else {
279 return pht(
280 'This object has an unknown or invalid policy setting ("%s").',
281 $policy);
286 public function getFullName() {
287 switch ($this->getType()) {
288 case PhabricatorPolicyType::TYPE_PROJECT:
289 return pht('Members of Project: %s', $this->getName());
290 case PhabricatorPolicyType::TYPE_MASKED:
291 return pht('Other: %s', $this->getName());
292 case PhabricatorPolicyType::TYPE_USER:
293 return pht('Only User: %s', $this->getName());
294 default:
295 return $this->getName();
299 public function newRef(PhabricatorUser $viewer) {
300 return id(new PhabricatorPolicyRef())
301 ->setViewer($viewer)
302 ->setPolicy($this);
305 public function isProjectPolicy() {
306 return ($this->getType() === PhabricatorPolicyType::TYPE_PROJECT);
309 public function isCustomPolicy() {
310 return ($this->getType() === PhabricatorPolicyType::TYPE_CUSTOM);
313 public function isMaskedPolicy() {
314 return ($this->getType() === PhabricatorPolicyType::TYPE_MASKED);
318 * Return a list of custom rule classes (concrete subclasses of
319 * @{class:PhabricatorPolicyRule}) this policy uses.
321 * @return list<string> List of class names.
323 public function getCustomRuleClasses() {
324 $classes = array();
326 foreach ($this->getRules() as $rule) {
327 if (!is_array($rule)) {
328 // This rule is invalid. We'll reject it later, but don't need to
329 // extract anything from it for now.
330 continue;
333 $class = idx($rule, 'rule');
334 try {
335 if (class_exists($class)) {
336 $classes[$class] = $class;
338 } catch (Exception $ex) {
339 continue;
343 return array_keys($classes);
347 * Return a list of all values used by a given rule class to implement this
348 * policy. This is used to bulk load data (like project memberships) in order
349 * to apply policy filters efficiently.
351 * @param string Policy rule classname.
352 * @return list<wild> List of values used in this policy.
354 public function getCustomRuleValues($rule_class) {
355 $values = array();
356 foreach ($this->getRules() as $rule) {
357 if ($rule['rule'] == $rule_class) {
358 $values[] = $rule['value'];
361 return $values;
364 public function attachRuleObjects(array $objects) {
365 $this->ruleObjects = $objects;
366 return $this;
369 public function getRuleObjects() {
370 return $this->assertAttached($this->ruleObjects);
375 * Return `true` if this policy is stronger (more restrictive) than some
376 * other policy.
378 * Because policies are complicated, determining which policies are
379 * "stronger" is not trivial. This method uses a very coarse working
380 * definition of policy strength which is cheap to compute, unambiguous,
381 * and intuitive in the common cases.
383 * This method returns `true` if the //class// of this policy is stronger
384 * than the other policy, even if the policies are (or might be) the same in
385 * practice. For example, "Members of Project X" is considered a stronger
386 * policy than "All Users", even though "Project X" might (in some rare
387 * cases) contain every user.
389 * Generally, the ordering here is:
391 * - Public
392 * - All Users
393 * - (Everything Else)
394 * - No One
396 * In the "everything else" bucket, we can't make any broad claims about
397 * which policy is stronger (and we especially can't make those claims
398 * cheaply).
400 * Even if we fully evaluated each policy, the two policies might be
401 * "Members of X" and "Members of Y", each of which permits access to some
402 * set of unique users. In this case, neither is strictly stronger than
403 * the other.
405 * @param PhabricatorPolicy Other policy.
406 * @return bool `true` if this policy is more restrictive than the other
407 * policy.
409 public function isStrongerThan(PhabricatorPolicy $other) {
410 $this_policy = $this->getPHID();
411 $other_policy = $other->getPHID();
413 $strengths = array(
414 PhabricatorPolicies::POLICY_PUBLIC => -2,
415 PhabricatorPolicies::POLICY_USER => -1,
416 // (Default policies have strength 0.)
417 PhabricatorPolicies::POLICY_NOONE => 1,
420 $this_strength = idx($strengths, $this->getPHID(), 0);
421 $other_strength = idx($strengths, $other->getPHID(), 0);
423 return ($this_strength > $other_strength);
426 public function isValidPolicyForEdit() {
427 return $this->getType() !== PhabricatorPolicyType::TYPE_MASKED;
430 public static function getSpecialRules(
431 PhabricatorPolicyInterface $object,
432 PhabricatorUser $viewer,
433 $capability,
434 $active_only) {
436 $exceptions = array();
437 if ($object instanceof PhabricatorPolicyCodexInterface) {
438 $codex = id(PhabricatorPolicyCodex::newFromObject($object, $viewer))
439 ->setCapability($capability);
440 $rules = $codex->getPolicySpecialRuleDescriptions();
442 foreach ($rules as $rule) {
443 $is_active = $rule->getIsActive();
444 if ($is_active) {
445 $rule_capabilities = $rule->getCapabilities();
446 if ($rule_capabilities) {
447 if (!in_array($capability, $rule_capabilities)) {
448 $is_active = false;
453 if (!$is_active && $active_only) {
454 continue;
457 $description = $rule->getDescription();
459 if (!$is_active) {
460 $description = phutil_tag(
461 'span',
462 array(
463 'class' => 'phui-policy-section-view-inactive-rule',
465 $description);
468 $exceptions[] = $description;
472 if (!$exceptions) {
473 if (method_exists($object, 'describeAutomaticCapability')) {
474 $exceptions = (array)$object->describeAutomaticCapability($capability);
475 $exceptions = array_filter($exceptions);
479 return $exceptions;
483 /* -( PhabricatorPolicyInterface )----------------------------------------- */
486 public function getCapabilities() {
487 return array(
488 PhabricatorPolicyCapability::CAN_VIEW,
492 public function getPolicy($capability) {
493 // NOTE: We implement policies only so we can comply with the interface.
494 // The actual query skips them, as enforcing policies on policies seems
495 // perilous and isn't currently required by the application.
496 return PhabricatorPolicies::POLICY_PUBLIC;
499 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
500 return false;
504 /* -( PhabricatorDestructibleInterface )----------------------------------- */
507 public function destroyObjectPermanently(
508 PhabricatorDestructionEngine $engine) {
510 $this->delete();