public function getSearchFields($object) { $fields = array(); if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) { $fields[] = id(new PhabricatorSpacesSearchField())->setKey('spacePHIDs')->setConduitKey('spaces')->setAliases(array('space', 'spaces'))->setLabel(pht('Spaces'))->setDescription(pht('Search for objects in certain spaces.')); } return $fields; }
public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView())->setViewer($viewer)->setActionList($this->newActions()); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions($viewer, $repository); $view_parts = array(); if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($repository); $view_parts[] = $viewer->renderHandle($space_phid); } $view_parts[] = $descriptions[PhabricatorPolicyCapability::CAN_VIEW]; $view->addProperty(pht('Visible To'), phutil_implode_html(" · ", $view_parts)); $view->addProperty(pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); $pushable = $repository->isHosted() ? $descriptions[DiffusionPushCapability::CAPABILITY] : phutil_tag('em', array(), pht('Not a Hosted Repository')); $view->addProperty(pht('Pushable By'), $pushable); return $this->newBox(pht('Policies'), $view); }
private function appendSpaceInformation(AphrontDialogView $dialog, PhabricatorPolicyInterface $object, PhabricatorPolicy $policy, $capability) { $viewer = $this->getViewer(); if (!$object instanceof PhabricatorSpacesInterface) { return; } if (!PhabricatorSpacesNamespaceQuery::getSpacesExist($viewer)) { return; } // NOTE: We're intentionally letting users through here, even if they only // have access to one space. The intent is to help users in "space jail" // understand who objects they create are visible to: $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); $handles = $viewer->loadHandles(array($space_phid)); $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); $dialog->appendParagraph(array(pht('This object is in %s, and can only be seen or edited by users with ' . 'access to view objects in the space.', $handles[$space_phid]->renderLink()), ' ', phutil_tag('strong', array(), phutil_tag('a', array('href' => $doc_href, 'target' => '_blank'), pht('Learn More'))))); $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if (!$space) { return; } $space_policies = PhabricatorPolicyQuery::loadPolicies($viewer, $space); $space_policy = idx($space_policies, PhabricatorPolicyCapability::CAN_VIEW); if (!$space_policy) { return; } $space_explanation = PhabricatorPolicy::getPolicyExplanation($viewer, $space_policy->getPHID()); $items = array(); $items[] = $space_explanation; foreach ($items as $key => $item) { $items[$key] = phutil_tag('li', array(), $item); } $dialog->appendParagraph(pht('Users who can see objects in this space:')); $dialog->appendChild(phutil_tag('ul', array(), $items)); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; if ($capability == $view_capability) { $stronger = $space_policy->isStrongerThan($policy); if ($stronger) { $dialog->appendParagraph(pht('The space this object is in has a more restrictive view ' . 'policy ("%s") than the object does ("%s"), so the space\'s ' . 'view policy is shown as a hint instead of the object policy.', $space_policy->getShortName(), $policy->getShortName())); } } $dialog->appendParagraph(pht('After a user passes space policy checks, they must still pass ' . 'object policy checks.')); }
public function render() { $object = $this->getObject(); $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); if (!$space_phid) { return null; } // If the viewer can't see spaces, pretend they don't exist. $viewer = $this->getUser(); if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) { return null; } // If this is the default space, don't show a space label. $default = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); if ($default) { if ($default->getPHID() == $space_phid) { return null; } } return phutil_tag('span', array('class' => 'spaces-name'), array($viewer->renderHandle($space_phid)->setUseShortName(true), ' | ')); }
protected final function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { $viewer = $this->getActor(); $object = $this->getMailReceiver(); $app_email = $this->getApplicationEmail(); $is_new = !$object->getID(); // If this is a new object which implements the Spaces interface and was // created by sending mail to an ApplicationEmail address, put the object // in the same Space the address is in. if ($is_new) { if ($object instanceof PhabricatorSpacesInterface) { if ($app_email) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($app_email); $object->setSpacePHID($space_phid); } } } $body_data = $mail->parseBody(); $body = $body_data['body']; $body = $this->enhanceBodyWithAttachments($body, $mail->getAttachments()); $xactions = $this->didReceiveMail($mail, $body); // If this object is subscribable, subscribe all the users who were // recipients on the message. if ($object instanceof PhabricatorSubscribableInterface) { $subscriber_phids = $mail->loadAllRecipientPHIDs(); if ($subscriber_phids) { $xactions[] = $this->newTransaction()->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)->setNewValue(array('+' => $subscriber_phids)); } } $command_xactions = $this->processMailCommands($mail, $body_data['commands']); foreach ($command_xactions as $xaction) { $xactions[] = $xaction; } if ($this->shouldCreateCommentFromMailBody()) { $comment = $this->newTransaction()->getApplicationTransactionCommentObject()->setContent($body); $xactions[] = $this->newTransaction()->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)->attachComment($comment); } $target = $object->getApplicationTransactionObject(); $this->newEditor($mail)->setContinueOnNoEffect(true)->applyTransactions($target, $xactions); }
public function buildCustomEditFields(PhabricatorEditEngine $engine, PhabricatorApplicationTransactionInterface $object) { $viewer = $engine->getViewer(); $editor = $object->getApplicationTransactionEditor(); $types = $editor->getTransactionTypesForObject($object); $types = array_fuse($types); $policies = id(new PhabricatorPolicyQuery())->setViewer($viewer)->setObject($object)->execute(); $map = array(PhabricatorTransactions::TYPE_VIEW_POLICY => array('key' => 'policy.view', 'aliases' => array('view'), 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 'label' => pht('View Policy'), 'description' => pht('Controls who can view the object.'), 'description.conduit' => pht('Change the view policy of the object.'), 'edit' => 'view'), PhabricatorTransactions::TYPE_EDIT_POLICY => array('key' => 'policy.edit', 'aliases' => array('edit'), 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 'label' => pht('Edit Policy'), 'description' => pht('Controls who can edit the object.'), 'description.conduit' => pht('Change the edit policy of the object.'), 'edit' => 'edit'), PhabricatorTransactions::TYPE_JOIN_POLICY => array('key' => 'policy.join', 'aliases' => array('join'), 'capability' => PhabricatorPolicyCapability::CAN_JOIN, 'label' => pht('Join Policy'), 'description' => pht('Controls who can join the object.'), 'description.conduit' => pht('Change the join policy of the object.'), 'edit' => 'join')); $fields = array(); foreach ($map as $type => $spec) { if (empty($types[$type])) { continue; } $capability = $spec['capability']; $key = $spec['key']; $aliases = $spec['aliases']; $label = $spec['label']; $description = $spec['description']; $conduit_description = $spec['description.conduit']; $edit = $spec['edit']; $policy_field = id(new PhabricatorPolicyEditField())->setKey($key)->setLabel($label)->setAliases($aliases)->setIsCopyable(true)->setCapability($capability)->setPolicies($policies)->setTransactionType($type)->setEditTypeKey($edit)->setDescription($description)->setConduitDescription($conduit_description)->setConduitTypeDescription(pht('New policy PHID or constant.'))->setValue($object->getPolicy($capability)); $fields[] = $policy_field; if ($object instanceof PhabricatorSpacesInterface) { if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { $type_space = PhabricatorTransactions::TYPE_SPACE; if (isset($types[$type_space])) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); $space_field = id(new PhabricatorSpaceEditField())->setKey('spacePHID')->setLabel(pht('Space'))->setEditTypeKey('space')->setIsCopyable(true)->setIsLockable(false)->setIsReorderable(false)->setAliases(array('space', 'policy.space'))->setTransactionType($type_space)->setDescription(pht('Select a space for the object.'))->setConduitDescription(pht('Shift the object between spaces.'))->setConduitTypeDescription(pht('New space PHID.'))->setValue($space_phid); $fields[] = $space_field; $space_field->setPolicyField($policy_field); $policy_field->setSpaceField($space_field); } } } } return $fields; }
private function buildSpaceSection(PhabricatorPolicyInterface $object, PhabricatorPolicy $policy, $capability) { $viewer = $this->getViewer(); if (!$object instanceof PhabricatorSpacesInterface) { return null; } if (!PhabricatorSpacesNamespaceQuery::getSpacesExist($viewer)) { return null; } $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if (!$space) { return null; } $space_policies = PhabricatorPolicyQuery::loadPolicies($viewer, $space); $space_policy = idx($space_policies, PhabricatorPolicyCapability::CAN_VIEW); if (!$space_policy) { return null; } $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); $capability_name = $this->getCapabilityName($capability); $space_section = id(new PHUIPolicySectionView())->setViewer($viewer)->setIcon('fa-th-large bluegrey')->setHeader(pht('Space'))->setDocumentationLink(pht('Spaces Documentation'), $doc_href)->appendList(array(array(phutil_tag('strong', array(), pht('Space:')), ' ', $viewer->renderHandle($space_phid)->setAsTag(true)), array(phutil_tag('strong', array(), pht('%s:', $capability_name)), ' ', $space_policy->getShortName())))->appendParagraph(pht('This object is in %s and can only be seen or edited by users ' . 'with access to view objects in the space.', $viewer->renderHandle($space_phid))); $space_explanation = PhabricatorPolicy::getPolicyExplanation($viewer, $space_policy->getPHID()); $items = array(); $items[] = $space_explanation; $space_section->appendParagraph(pht('Users who can see objects in this space:'))->appendList($items); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; if ($capability == $view_capability) { $stronger = $space_policy->isStrongerThan($policy); if ($stronger) { $space_section->appendHint(pht('The space this object is in has a more restrictive view ' . 'policy ("%s") than the object does ("%s"), so the space\'s ' . 'view policy is shown as a hint instead of the object policy.', $space_policy->getShortName(), $policy->getShortName())); } } $space_section->appendHint(pht('After a user passes space policy checks, they must still pass ' . 'object policy checks.')); return $space_section; }
private function buildEmailTable($is_edit, $highlight) { $viewer = $this->getViewer(); $application = $this->getApplication(); $uri = new PhutilURI($this->getPanelURI()); $emails = id(new PhabricatorMetaMTAApplicationEmailQuery())->setViewer($viewer)->withApplicationPHIDs(array($application->getPHID()))->execute(); $rowc = array(); $rows = array(); foreach ($emails as $email) { $button_edit = javelin_tag('a', array('class' => 'button small grey', 'href' => $uri->alter('edit', $email->getID()), 'sigil' => 'workflow'), pht('Edit')); $button_remove = javelin_tag('a', array('class' => 'button small grey', 'href' => $uri->alter('delete', $email->getID()), 'sigil' => 'workflow'), pht('Delete')); if ($highlight == $email->getID()) { $rowc[] = 'highlighted'; } else { $rowc[] = null; } $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($email); if ($space_phid) { $email_space = $viewer->renderHandle($space_phid); } else { $email_space = null; } $rows[] = array($email_space, $email->getAddress(), $button_edit, $button_remove); } $table = id(new AphrontTableView($rows))->setNoDataString(pht('No application emails created yet.')); $table->setHeaders(array(pht('Space'), pht('Email'), pht('Edit'), pht('Delete'))); $table->setColumnClasses(array('', 'wide', 'action', 'action')); $table->setRowClasses($rowc); $table->setColumnVisibility(array(PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer), true, $is_edit, $is_edit)); return $table; }
private function validateSpaceTransactions(PhabricatorLiskDAO $object, array $xactions, $transaction_type) { $errors = array(); $actor = $this->getActor(); $has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor); $actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor); $active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces($actor); foreach ($xactions as $xaction) { $space_phid = $xaction->getNewValue(); if ($space_phid === null) { if (!$has_spaces) { // The install doesn't have any spaces, so this is fine. continue; } // The install has some spaces, so every object needs to be put // in a valid space. $errors[] = new PhabricatorApplicationTransactionValidationError($transaction_type, pht('Invalid'), pht('You must choose a space for this object.'), $xaction); continue; } // If the PHID isn't `null`, it needs to be a valid space that the // viewer can see. if (empty($actor_spaces[$space_phid])) { $errors[] = new PhabricatorApplicationTransactionValidationError($transaction_type, pht('Invalid'), pht('You can not shift this object in the selected space, because ' . 'the space does not exist or you do not have access to it.'), $xaction); } else { if (empty($active_spaces[$space_phid])) { // It's OK to edit objects in an archived space, so just move on if // we aren't adjusting the value. $old_space_phid = $this->getTransactionOldValue($object, $xaction); if ($space_phid == $old_space_phid) { continue; } $errors[] = new PhabricatorApplicationTransactionValidationError($transaction_type, pht('Archived'), pht('You can not shift this object into the selected space, because ' . 'the space is archived. Objects can not be created inside (or ' . 'moved into) archived spaces.'), $xaction); } } } return $errors; }
public function getDefaultSpacePHID() { // TODO: We might let the user switch which space they're "in" later on; // for now just use the global space if one exists. // If the viewer has access to the default space, use that. $spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces($this); foreach ($spaces as $space) { if ($space->getIsDefaultNamespace()) { return $space->getPHID(); } } // Otherwise, use the space with the lowest ID that they have access to. // This just tends to keep the default stable and predictable over time, // so adding a new space won't change behavior for users. if ($spaces) { $spaces = msort($spaces, 'getID'); return head($spaces)->getPHID(); } return null; }
protected function buildSearchFields() { $fields = array(); foreach ($this->buildCustomSearchFields() as $field) { $fields[] = $field; } $object = $this->newResultObject(); if ($object) { if ($object instanceof PhabricatorSubscribableInterface) { $fields[] = id(new PhabricatorSearchSubscribersField())->setLabel(pht('Subscribers'))->setKey('subscriberPHIDs')->setAliases(array('subscriber', 'subscribers')); } if ($object instanceof PhabricatorProjectInterface) { $fields[] = id(new PhabricatorProjectSearchField())->setKey('projectPHIDs')->setAliases(array('project', 'projects'))->setLabel(pht('Projects')); } if ($object instanceof PhabricatorSpacesInterface) { if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) { $fields[] = id(new PhabricatorSpacesSearchField())->setKey('spacePHIDs')->setAliases(array('space', 'spaces'))->setLabel(pht('Spaces')); } } } foreach ($this->buildCustomFieldSearchFields() as $custom_field) { $fields[] = $custom_field; } $query = $this->newQuery(); if ($query && $this->shouldShowOrderField()) { $orders = $query->getBuiltinOrders(); $orders = ipull($orders, 'name'); $fields[] = id(new PhabricatorSearchOrderField())->setLabel(pht('Order By'))->setKey('order')->setOrderAliases($query->getBuiltinOrderAliasMap())->setOptions($orders); } $field_map = array(); foreach ($fields as $field) { $key = $field->getKey(); if (isset($field_map[$key])) { throw new Exception(pht('Two fields in this SearchEngine use the same key ("%s"), but ' . 'each field must use a unique key.', $key)); } $field_map[$key] = $field; } return $field_map; }
private function buildTransactions($actions, ManiphestTask $task) { $value_map = array(); $type_map = array('add_comment' => PhabricatorTransactions::TYPE_COMMENT, 'assign' => ManiphestTransaction::TYPE_OWNER, 'status' => ManiphestTransaction::TYPE_STATUS, 'priority' => ManiphestTransaction::TYPE_PRIORITY, 'add_project' => PhabricatorTransactions::TYPE_EDGE, 'remove_project' => PhabricatorTransactions::TYPE_EDGE, 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 'space' => PhabricatorTransactions::TYPE_SPACE); $edge_edit_types = array('add_project' => true, 'remove_project' => true, 'add_ccs' => true, 'remove_ccs' => true); $xactions = array(); foreach ($actions as $action) { if (empty($type_map[$action['action']])) { throw new Exception(pht("Unknown batch edit action '%s'!", $action)); } $type = $type_map[$action['action']]; // Figure out the current value, possibly after modifications by other // batch actions of the same type. For example, if the user chooses to // "Add Comment" twice, we should add both comments. More notably, if the // user chooses "Remove Project..." and also "Add Project...", we should // avoid restoring the removed project in the second transaction. if (array_key_exists($type, $value_map)) { $current = $value_map[$type]; } else { switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: $current = null; break; case ManiphestTransaction::TYPE_OWNER: $current = $task->getOwnerPHID(); break; case ManiphestTransaction::TYPE_STATUS: $current = $task->getStatus(); break; case ManiphestTransaction::TYPE_PRIORITY: $current = $task->getPriority(); break; case PhabricatorTransactions::TYPE_EDGE: $current = $task->getProjectPHIDs(); break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $current = $task->getSubscriberPHIDs(); break; case PhabricatorTransactions::TYPE_SPACE: $current = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($task); break; } } // Check if the value is meaningful / provided, and normalize it if // necessary. This discards, e.g., empty comments and empty owner // changes. $value = $action['value']; switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: if (!strlen($value)) { continue 2; } break; case PhabricatorTransactions::TYPE_SPACE: if (empty($value)) { continue 2; } $value = head($value); break; case ManiphestTransaction::TYPE_OWNER: if (empty($value)) { continue 2; } $value = head($value); $no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN; if ($value === $no_owner) { $value = null; } break; case PhabricatorTransactions::TYPE_EDGE: if (empty($value)) { continue 2; } break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: if (empty($value)) { continue 2; } break; } // If the edit doesn't change anything, go to the next action. This // check is only valid for changes like "owner", "status", etc, not // for edge edits, because we should still apply an edit like // "Remove Projects: A, B" to a task with projects "A, B". if (empty($edge_edit_types[$action['action']])) { if ($value == $current) { continue; } } // Apply the value change; for most edits this is just replacement, but // some need to merge the current and edited values (add/remove project). switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: if (strlen($current)) { $value = $current . "\n\n" . $value; } break; case PhabricatorTransactions::TYPE_EDGE: $is_remove = $action['action'] == 'remove_project'; $current = array_fill_keys($current, true); $value = array_fill_keys($value, true); $new = $current; $did_something = false; if ($is_remove) { foreach ($value as $phid => $ignored) { if (isset($new[$phid])) { unset($new[$phid]); $did_something = true; } } } else { foreach ($value as $phid => $ignored) { if (empty($new[$phid])) { $new[$phid] = true; $did_something = true; } } } if (!$did_something) { continue 2; } $value = array_keys($new); break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $is_remove = $action['action'] == 'remove_ccs'; $current = array_fill_keys($current, true); $new = array(); $did_something = false; if ($is_remove) { foreach ($value as $phid) { if (isset($current[$phid])) { $new[$phid] = true; $did_something = true; } } if ($new) { $value = array('-' => array_keys($new)); } } else { $new = array(); foreach ($value as $phid) { $new[$phid] = true; $did_something = true; } if ($new) { $value = array('+' => array_keys($new)); } } if (!$did_something) { continue 2; } break; } $value_map[$type] = $value; } $template = new ManiphestTransaction(); foreach ($value_map as $type => $value) { $xaction = clone $template; $xaction->setTransactionType($type); switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: $xaction->attachComment(id(new ManiphestTransactionComment())->setContent($value)); break; case PhabricatorTransactions::TYPE_EDGE: $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xaction->setMetadataValue('edge:type', $project_type)->setNewValue(array('=' => array_fuse($value))); break; default: $xaction->setNewValue($value); break; } $xactions[] = $xaction; } return $xactions; }
public function getHeraldField($field_name) { switch ($field_name) { case self::FIELD_RULE: return null; case self::FIELD_CONTENT_SOURCE: return $this->getContentSource()->getSource(); case self::FIELD_ALWAYS: return true; case self::FIELD_IS_NEW_OBJECT: return $this->getIsNewObject(); case self::FIELD_CC: $object = $this->getObject(); if (!$object instanceof PhabricatorSubscribableInterface) { throw new Exception(pht('Adapter object (of class "%s") does not implement interface ' . '"%s", so the subscribers field value can not be determined.', get_class($object), 'PhabricatorSubscribableInterface')); } $phid = $object->getPHID(); return PhabricatorSubscribersQuery::loadSubscribersForPHID($phid); case self::FIELD_APPLICATION_EMAIL: $value = array(); // while there is only one match by implementation, we do set // comparisons on phids, so return an array with just the phid if ($this->getApplicationEmail()) { $value[] = $this->getApplicationEmail()->getPHID(); } return $value; case self::FIELD_SPACE: $object = $this->getObject(); if (!$object instanceof PhabricatorSpacesInterface) { throw new Exception(pht('Adapter object (of class "%s") does not implement interface ' . '"%s", so the Space field value can not be determined.', get_class($object), 'PhabricatorSpacesInterface')); } return PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); default: if ($this->isHeraldCustomKey($field_name)) { return $this->getCustomFieldValue($field_name); } throw new Exception(pht("Unknown field '%s'!", $field_name)); } }
private function destroyAllSpaces() { PhabricatorSpacesNamespaceQuery::destroySpacesCache(); $spaces = $this->loadAllSpaces(); foreach ($spaces as $space) { $engine = new PhabricatorDestructionEngine(); $engine->destroyObject($space); } }
public function getHeraldFieldValue($object) { return PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); }
private function renderPolicyProperty(PhabricatorPolicyInterface $object) { $viewer = $this->getUser(); $policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; $policy = idx($policies, $view_capability); if (!$policy) { return null; } // If an object is in a Space with a strictly stronger (more restrictive) // policy, we show the more restrictive policy. This better aligns the // UI hint with the actual behavior. // NOTE: We'll do this even if the viewer has access to only one space, and // show them information about the existence of spaces if they click // through. $use_space_policy = false; if ($object instanceof PhabricatorSpacesInterface) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID($object); $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if ($space) { $space_policies = PhabricatorPolicyQuery::loadPolicies($viewer, $space); $space_policy = idx($space_policies, $view_capability); if ($space_policy) { if ($space_policy->isStrongerThan($policy)) { $policy = $space_policy; $use_space_policy = true; } } } } $container_classes = array(); $container_classes[] = 'policy-header-callout'; $phid = $object->getPHID(); // If we're going to show the object policy, try to determine if the object // policy differs from the default policy. If it does, we'll call it out // as changed. if (!$use_space_policy) { $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject($viewer, $object, $view_capability); if ($default_policy) { if ($default_policy->getPHID() != $policy->getPHID()) { $container_classes[] = 'policy-adjusted'; if ($default_policy->isStrongerThan($policy)) { // The policy has strictly been weakened. For example, the // default might be "All Users" and the current policy is "Public". $container_classes[] = 'policy-adjusted-weaker'; } else { if ($policy->isStrongerThan($default_policy)) { // The policy has strictly been strengthened, and is now more // restrictive than the default. For example, "All Users" has // been replaced with "No One". $container_classes[] = 'policy-adjusted-stronger'; } else { // The policy has been adjusted but not strictly strengthened // or weakened. For example, "Members of X" has been replaced with // "Members of Y". $container_classes[] = 'policy-adjusted-different'; } } } } } $icon = id(new PHUIIconView())->setIcon($policy->getIcon() . ' bluegrey'); $link = javelin_tag('a', array('class' => 'policy-link', 'href' => '/policy/explain/' . $phid . '/' . $view_capability . '/', 'sigil' => 'workflow'), $policy->getShortName()); return phutil_tag('span', array('class' => implode(' ', $container_classes)), array($icon, $link)); }
private function canViewerSeeObjectsInSpace(PhabricatorUser $viewer, $space_phid) { $spaces = PhabricatorSpacesNamespaceQuery::getAllSpaces(); // If there are no spaces, everything exists in an implicit default space // with no policy controls. This is the default state. if (!$spaces) { if ($space_phid !== null) { return false; } else { return true; } } if ($space_phid === null) { $space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); } else { $space = idx($spaces, $space_phid); } if (!$space) { return false; } // This may be more involved later, but for now being able to see the // space is equivalent to being able to see everything in it. return self::hasCapability($viewer, $space, PhabricatorPolicyCapability::CAN_VIEW); }
/** * Constrain the query to include only results in valid Spaces. * * This method builds part of a WHERE clause which considers the spaces the * viewer has access to see with any explicit constraint on spaces added by * @{method:withSpacePHIDs}. * * @param AphrontDatabaseConnection Database connection. * @return string Part of a WHERE clause. * @task spaces */ private function buildSpacesWhereClause(AphrontDatabaseConnection $conn) { $object = $this->newResultObject(); if (!$object) { return null; } if (!$object instanceof PhabricatorSpacesInterface) { return null; } $viewer = $this->getViewer(); // If we have an omnipotent viewer and no formal space constraints, don't // emit a clause. This primarily enables older migrations to run cleanly, // without fataling because they try to match a `spacePHID` column which // does not exist yet. See T8743, T8746. if ($viewer->isOmnipotent()) { if ($this->spaceIsArchived === null && $this->spacePHIDs === null) { return null; } } $space_phids = array(); $include_null = false; $all = PhabricatorSpacesNamespaceQuery::getAllSpaces(); if (!$all) { // If there are no spaces at all, implicitly give the viewer access to // the default space. $include_null = true; } else { // Otherwise, give them access to the spaces they have permission to // see. $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); foreach ($viewer_spaces as $viewer_space) { if ($this->spaceIsArchived !== null) { if ($viewer_space->getIsArchived() != $this->spaceIsArchived) { continue; } } $phid = $viewer_space->getPHID(); $space_phids[$phid] = $phid; if ($viewer_space->getIsDefaultNamespace()) { $include_null = true; } } } // If we have additional explicit constraints, evaluate them now. if ($this->spacePHIDs !== null) { $explicit = array(); $explicit_null = false; foreach ($this->spacePHIDs as $phid) { if ($phid === null) { $space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); } else { $space = idx($all, $phid); } if ($space) { $phid = $space->getPHID(); $explicit[$phid] = $phid; if ($space->getIsDefaultNamespace()) { $explicit_null = true; } } } // If the viewer can see the default space but it isn't on the explicit // list of spaces to query, don't match it. if ($include_null && !$explicit_null) { $include_null = false; } // Include only the spaces common to the viewer and the constraints. $space_phids = array_intersect_key($space_phids, $explicit); } if (!$space_phids && !$include_null) { if ($this->spacePHIDs === null) { throw new PhabricatorEmptyQueryException(pht('You do not have access to any spaces.')); } else { throw new PhabricatorEmptyQueryException(pht('You do not have access to any of the spaces this query ' . 'is constrained to.')); } } $alias = $this->getPrimaryTableAlias(); if ($alias) { $col = qsprintf($conn, '%T.spacePHID', $alias); } else { $col = 'spacePHID'; } if ($space_phids && $include_null) { return qsprintf($conn, '(%Q IN (%Ls) OR %Q IS NULL)', $col, $space_phids, $col); } else { if ($space_phids) { return qsprintf($conn, '%Q IN (%Ls)', $col, $space_phids); } else { return qsprintf($conn, '%Q IS NULL', $col); } } }
public function willBeginExecution() { $request = $this->getRequest(); if ($request->getUser()) { // NOTE: Unit tests can set a user explicitly. Normal requests are not // permitted to do this. PhabricatorTestCase::assertExecutingUnitTests(); $user = $request->getUser(); } else { $user = new PhabricatorUser(); $session_engine = new PhabricatorAuthSessionEngine(); $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); if (strlen($phsid)) { $session_user = $session_engine->loadUserForSession(PhabricatorAuthSession::TYPE_WEB, $phsid); if ($session_user) { $user = $session_user; } } else { // If the client doesn't have a session token, generate an anonymous // session. This is used to provide CSRF protection to logged-out users. $phsid = $session_engine->establishSession(PhabricatorAuthSession::TYPE_WEB, null, $partial = false); // This may be a resource request, in which case we just don't set // the cookie. if ($request->canSetCookies()) { $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid); } } if (!$user->isLoggedIn()) { $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid)); } $request->setUser($user); } PhabricatorEnv::setLocaleCode($user->getTranslation()); $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } // NOTE: We want to set up the user first so we can render a real page // here, but fire this before any real logic. $restricted = array('code'); foreach ($restricted as $parameter) { if ($request->getExists($parameter)) { if (!$this->shouldAllowRestrictedParameter($parameter)) { throw new Exception(pht('Request includes restricted parameter "%s", but this ' . 'controller ("%s") does not whitelist it. Refusing to ' . 'serve this request because it might be part of a redirection ' . 'attack.', $parameter, get_class($this))); } } } if ($this->shouldRequireEnabledUser()) { if ($user->isLoggedIn() && !$user->getIsApproved()) { $controller = new PhabricatorAuthNeedsApprovalController(); return $this->delegateToController($controller); } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); } } $auth_class = 'PhabricatorAuthApplication'; $auth_application = PhabricatorApplication::getByClass($auth_class); // Require partial sessions to finish login before doing anything. if (!$this->shouldAllowPartialSessions()) { if ($user->hasSession() && $user->getSession()->getIsPartial()) { $login_controller = new PhabricatorAuthFinishController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } } // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); if ($need_mfa && !$have_mfa) { // Check if the cache is just out of date. Otherwise, roadblock the user // and require MFA enrollment. $user->updateMultiFactorEnrollment(); if (!$user->getIsEnrolledInMultiFactor()) { $mfa_controller = new PhabricatorAuthNeedsMultiFactorController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($mfa_controller); } } if ($this->shouldRequireLogin()) { // This actually means we need either: // - a valid user, or a public controller; and // - permission to see the application; and // - permission to see at least one Space if spaces are configured. $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { $login_controller = new PhabricatorAuthStartController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { if (!$user->getIsEmailVerified()) { $controller = new PhabricatorMustVerifyEmailController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } } } // If Spaces are configured, require that the user have access to at // least one. If we don't do this, they'll get confusing error messages // later on. $spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist(); if ($spaces) { $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($user); if (!$viewer_spaces) { $controller = new PhabricatorSpacesNoAccessController(); return $this->delegateToController($controller); } } // If the user doesn't have access to the application, don't let them use // any of its controllers. We query the application in order to generate // a policy exception if the viewer doesn't have permission. $application = $this->getCurrentApplication(); if ($application) { id(new PhabricatorApplicationQuery())->setViewer($user)->withPHIDs(array($application->getPHID()))->executeOne(); } } if (!$this->shouldAllowLegallyNonCompliantUsers()) { $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad = id(new PhabricatorApplicationQuery())->setViewer($user)->withClasses(array($legalpad_class))->withInstalled(true)->execute(); $legalpad = head($legalpad); $doc_query = id(new LegalpadDocumentQuery())->setViewer($user)->withSignatureRequired(1)->needViewerSignatures(true); if ($user->hasSession() && !$user->getSession()->getIsPartial() && !$user->getSession()->getSignedLegalpadDocuments() && $user->isLoggedIn() && $legalpad) { $sign_docs = $doc_query->execute(); $must_sign_docs = array(); foreach ($sign_docs as $sign_doc) { if (!$sign_doc->getUserSignature($user->getPHID())) { $must_sign_docs[] = $sign_doc; } } if ($must_sign_docs) { $controller = new LegalpadDocumentSignController(); $this->getRequest()->setURIMap(array('id' => head($must_sign_docs)->getID())); $this->setCurrentApplication($legalpad); return $this->delegateToController($controller); } else { $engine = id(new PhabricatorAuthSessionEngine())->signLegalpadDocuments($user, $sign_docs); } } } // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } }
private function buildSpacesControl() { if ($this->capability != PhabricatorPolicyCapability::CAN_VIEW) { return null; } if (!$this->object instanceof PhabricatorSpacesInterface) { return null; } $viewer = $this->getUser(); if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) { return null; } $space_phid = $this->getSpacePHID(); if ($space_phid === null) { $space_phid = $viewer->getDefaultSpacePHID(); } $select = AphrontFormSelectControl::renderSelectTag($space_phid, PhabricatorSpacesNamespaceQuery::getSpaceOptionsForViewer($viewer, $space_phid), array('disabled' => $this->getDisabled(), 'name' => 'spacePHID', 'class' => 'aphront-space-select-control-knob')); return $select; }