3 final class PhabricatorPolicy
4 extends PhabricatorPolicyDAO
6 PhabricatorPolicyInterface
,
7 PhabricatorDestructibleInterface
{
9 const ACTION_ALLOW
= 'allow';
10 const ACTION_DENY
= 'deny';
19 protected $rules = array();
20 protected $defaultAction = self
::ACTION_DENY
;
22 private $ruleObjects = self
::ATTACHABLE
;
24 protected function getConfiguration() {
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(
36 'columns' => array('phid'),
40 ) + parent
::getConfiguration();
43 public function generatePHID() {
44 return PhabricatorPHID
::generateNewPHID(
45 PhabricatorPolicyPHIDTypePolicy
::TYPECONST
);
48 public static function newFromPolicyAndHandle(
50 PhabricatorObjectHandle
$handle = null) {
52 $is_global = PhabricatorPolicyQuery
::isGlobalPolicy($policy_identifier);
54 return PhabricatorPolicyQuery
::getGlobalPolicy($policy_identifier);
57 $policy = PhabricatorPolicyQuery
::getObjectPolicy($policy_identifier);
65 "Policy identifier is an object PHID ('%s'), but no object handle ".
66 "was provided. A handle must be provided for object policies.",
70 $handle_phid = $handle->getPHID();
71 if ($policy_identifier != $handle_phid) {
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.",
81 $policy = id(new PhabricatorPolicy())
82 ->setPHID($policy_identifier)
83 ->setHref($handle->getURI());
85 $phid_type = phid_get_type($policy_identifier);
87 case PhabricatorProjectProjectPHIDType
::TYPECONST
:
89 ->setType(PhabricatorPolicyType
::TYPE_PROJECT
)
90 ->setName($handle->getName())
91 ->setIcon($handle->getIcon());
93 case PhabricatorPeopleUserPHIDType
::TYPECONST
:
94 $policy->setType(PhabricatorPolicyType
::TYPE_USER
);
95 $policy->setName($handle->getFullName());
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
104 $policy->setType(PhabricatorPolicyType
::TYPE_MASKED
);
105 $policy->setName($handle->getFullName());
109 $policy->makeEphemeral();
114 public function setType($type) {
119 public function getType() {
121 return PhabricatorPolicyType
::TYPE_CUSTOM
;
126 public function setName($name) {
131 public function getName() {
133 return pht('Custom Policy');
138 public function setShortName($short_name) {
139 $this->shortName
= $short_name;
143 public function getShortName() {
144 if ($this->shortName
) {
145 return $this->shortName
;
147 return $this->getName();
150 public function setHref($href) {
155 public function getHref() {
159 public function setWorkflow($workflow) {
160 $this->workflow
= $workflow;
164 public function getWorkflow() {
165 return $this->workflow
;
168 public function setIcon($icon) {
173 public function getIcon() {
178 switch ($this->getType()) {
179 case PhabricatorPolicyType
::TYPE_GLOBAL
:
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
:
189 case PhabricatorPolicyType
::TYPE_PROJECT
:
190 return 'fa-briefcase';
191 case PhabricatorPolicyType
::TYPE_CUSTOM
:
192 case PhabricatorPolicyType
::TYPE_MASKED
:
193 return 'fa-certificate';
195 return 'fa-question-circle';
199 public function getSortKey() {
202 PhabricatorPolicyType
::getPolicyTypeOrder($this->getType()),
203 $this->getSortName());
206 private function getSortName() {
207 if ($this->getType() == PhabricatorPolicyType
::TYPE_GLOBAL
) {
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,
223 $type = phid_get_type($policy);
224 if ($type === PhabricatorProjectProjectPHIDType
::TYPECONST
) {
225 $handle = id(new PhabricatorHandleQuery())
227 ->withPHIDs(array($policy))
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,
242 $rule = PhabricatorPolicyQuery
::getObjectPolicyRule($policy);
244 return $rule->getPolicyExplanation();
248 case PhabricatorPolicies
::POLICY_PUBLIC
:
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.');
259 $handle = id(new PhabricatorHandleQuery())
261 ->withPHIDs(array($policy))
264 $type = phid_get_type($policy);
265 if ($type == PhabricatorProjectProjectPHIDType
::TYPECONST
) {
267 'Members of a particular project can take this action. (You '.
268 'can not see this object, so the name of this project is '.
270 } else if ($type == PhabricatorPeopleUserPHIDType
::TYPECONST
) {
272 '%s can take this action.',
273 $handle->getFullName());
274 } else if ($type == PhabricatorPolicyPHIDTypePolicy
::TYPECONST
) {
276 'This object has a custom policy controlling who can take this '.
280 'This object has an unknown or invalid policy setting ("%s").',
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());
295 return $this->getName();
299 public function newRef(PhabricatorUser
$viewer) {
300 return id(new PhabricatorPolicyRef())
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() {
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.
333 $class = idx($rule, 'rule');
335 if (class_exists($class)) {
336 $classes[$class] = $class;
338 } catch (Exception
$ex) {
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) {
356 foreach ($this->getRules() as $rule) {
357 if ($rule['rule'] == $rule_class) {
358 $values[] = $rule['value'];
364 public function attachRuleObjects(array $objects) {
365 $this->ruleObjects
= $objects;
369 public function getRuleObjects() {
370 return $this->assertAttached($this->ruleObjects
);
375 * Return `true` if this policy is stronger (more restrictive) than some
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:
393 * - (Everything Else)
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
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
405 * @param PhabricatorPolicy Other policy.
406 * @return bool `true` if this policy is more restrictive than the other
409 public function isStrongerThan(PhabricatorPolicy
$other) {
410 $this_policy = $this->getPHID();
411 $other_policy = $other->getPHID();
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,
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();
445 $rule_capabilities = $rule->getCapabilities();
446 if ($rule_capabilities) {
447 if (!in_array($capability, $rule_capabilities)) {
453 if (!$is_active && $active_only) {
457 $description = $rule->getDescription();
460 $description = phutil_tag(
463 'class' => 'phui-policy-section-view-inactive-rule',
468 $exceptions[] = $description;
473 if (method_exists($object, 'describeAutomaticCapability')) {
474 $exceptions = (array)$object->describeAutomaticCapability($capability);
475 $exceptions = array_filter($exceptions);
483 /* -( PhabricatorPolicyInterface )----------------------------------------- */
486 public function getCapabilities() {
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) {
504 /* -( PhabricatorDestructibleInterface )----------------------------------- */
507 public function destroyObjectPermanently(
508 PhabricatorDestructionEngine
$engine) {