Page MenuHomePhabricator

Tweak application and maniphest editors to handle policy corner cases better
ClosedPublic

Authored by btrahan on Mar 12 2014, 9:47 PM.
Tags
None
Referenced Files
Unknown Object (File)
Sat, Apr 6, 8:58 PM
Unknown Object (File)
Sat, Apr 6, 8:58 PM
Unknown Object (File)
Sat, Apr 6, 8:58 PM
Unknown Object (File)
Sat, Apr 6, 8:58 PM
Unknown Object (File)
Sat, Apr 6, 8:58 PM
Unknown Object (File)
Sat, Apr 6, 8:58 PM
Unknown Object (File)
Sat, Apr 6, 8:36 PM
Unknown Object (File)
Sat, Apr 6, 8:15 PM

Details

Summary

Fixes T4362. If you have a default edit + view policy of "no one" things get weird when you try to create a task - basically its impossible.

Ergo, re-jigger how we do policy checks just a bit.

  • if its a new object, don't bother with the "can the $actor edit this thing by virtue of having can see / can edit priveleges?" That makes no sense on create.
  • add a hook so when doing the "will $actor still be able to edit this thing after all the edits" checks the object can be updated to its ultimate state. This matters for Maniphest as being the owner lets you do all sorts of stuff.
Test Plan
  • made a task with no one policy and assigned to no one - exception
  • made a task with no one policy and assigned to me - success
    • made a comment on the task - success
    • reassigned the task to another user - exception
    • reassigned the task to another user and updated policies to "users" - success

Diff Detail

Repository
rP Phabricator
Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

btrahan retitled this revision from to [Probs needs changes] - tweak Maniphest and Application editors to handle policy corner cases better.
btrahan updated this object.
btrahan edited the test plan for this revision. (Show Details)
btrahan added a reviewer: epriestley.
src/applications/maniphest/editor/ManiphestTransactionEditor.php
108

whoops, will fix

I think a clean / general way to do this is:

  • Don't perform the can view / can edit checks when creating a new object.
  • In validateTransaction(), add a check for both TYPE_VIEW_POLICY and TYPE_EDIT_POLICY, which executes only if $xactions is empty and the object is new -- something like this:

    if ($is_new) { if (!$xactions) { if (!$object->hasCapability(...)) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'The selected view/edit policy excludes you. Choose a view/edit '. 'policy which allows you to view/edit the object.')); } } }

Then I think our bases are covered:

  • When editing an existing object, the current edit policy will be checked in requireCapabilities().
  • When editing an existing object, the new edit policy (if you set one) will be checked in validateTransaction().
  • When editing an existing object, the current view policy will be checked in validateEditParameters().
  • When editing an existing object, the new view policy should be checked in validateTransaction(), although it currently is not (it should look like the similar EDIT block there).
  • When creating a new object and not changing a policy, we'll check the default policy in the new block introduced above.
  • When creating a new object and changing the policy, we'll check the selected policy instead.

That might not cover everything, but I can't think of reasons why it won't work offhand.

src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
900–925

These checks, specifically.

I guess a reason that doesn't work is that it doesn't let "Owner" punch a hole through policies, since the new owner won't be set yet.

Instead of putting the new block in validateTransaction(), we could add it between applyInternalEffects() and applyExternalEffects(), and take another shot at raising a PhabricatorApplicationTransactionValidationException there. That would let us catch any policy issues that don't depend on external changes. I feel like we're getting into dangerous territory if we do it any later, since rollback won't necessarily be effective against applyExternalEffects().

Alternatively, we could make these checks explicit and let the application Editor classes handle special cases. I think that would look something like:

  • Implement something like adjustObjectForPolicyChecks(), which returns clone $object by default.
  • Maniphest would walk through the transactions and apply any owner changes.
  • Then, force policy check that object.
  • More complex objects have a reasonable chance to do something magical-but-not-too-gross here?

I kind of favor the last approach, so my recommendation overall is:

  • Disable the base view/edit checks for new objects. They aren't meaningful or correct.
  • Rely on the PhabricatorPolicyFilter::requireCapabilityWithForcedPolicy() call (or, for new objects with no relevant transactions, a new requireCapability() call) to do all the other validation, but we're going to pass it a fake $object which the Editor can monkey with first.
  • The Maniphest editor monkeys with the object by setting the owner.

If that makes zero sense, I can shoot you a skeleton diff for this.

I had some network transience so I saw https://secure.phabricator.com/D8508#8 and then went to work for a a few hours, heh. I assumed that you wanted me to still do ManiphestEditor hyjinx as I have them here, just confined to owner stuff.

I think I get the suggested approach but I think there is a fundamental problem here that I also hit in my other approach.

There is the actor, the old owner, the new owner, the old policy, and the new policy. The editor has the goals of 1) making sure the actor has the proper view / edit privileges relative to policy to begin with and 2) making sure actor doesn't change old => new policy such that they have lost privileges.

Consider the case where the actor is the old owner with a policy of no one, and then they change both the owner to someone else *AND* the policy to (e.g.) users.

If the Maniphest Editor just adjusts the owner from the actor to the new owner (leaving policy as no-one for the purposes of policy checks), the check will fail since the actor is no longer owner and no longer punching through that policy. If both owner and the policy are adjusted, policy is not enforced.

Its almost like we need to check the initial state (1 from above) - isn't this done in the controller via the query method for CAN_EDIT, CAN_VIEW permissions to lead the object to begin with? - then check the "final" proposed state in the editor (2 from above).

This would mean in the adjusting method to adjust both the new owner and new policy before any policy checks, and policy checks would have to become more generic to something like "the view policy you set let's no one see the task; try again."

So I guess I would propose:

  • MAYBE - adding a check for can_edit / can_view permissions first thing-ish in applyTransactions; this is unnecessary if we are sure we have query discipline in the controller or we could tackle it by adding such discipline
  • basically rock the adjustObjectForPolicyChecks approach, but
    • consolidate any and all policy filtering logic to around lines 599 in the diff as is
    • have the adjustObjectForPolicyChecks base implementation update the object's policy
      • have some hook method that gets handed this and returns it be default. subclasses like Maniphest should do more stuff like set the owner phid as appropos

If the Maniphest Editor just adjusts the owner from the actor to the new owner (leaving policy as no-one for the purposes of policy checks), the check will fail since the actor is no longer owner and no longer punching through that policy. If both owner and the policy are adjusted, policy is not enforced.

The policy checks use requireCapabilityWithForcedPolicy() to check the policy as though the edit had applied, so the check will be with the new owner and with the new policy. I'm not clear on the second half -- it seems like policy is enforced?

adding a check for can_edit / can_view permissions first thing-ish in applyTransactions

We basically already have this, I think? In validateEditParameters() (view check) / requireCapabilities() (soft edit check)?

have the adjustObjectForPolicyChecks base implementation update the object's policy

requireCapabilityWithForcedPolicy() should make this unnecessary (it's magic which pretends we updated the policy).

btrahan edited edge metadata.

i just need to blow all this away but checkpointing it via an arc diff

...I think this is what you mean?...

I still combined the "scenario one" checks - can $actor edit this bad boy because they have view / edit policy priveleges? - into one spot.

btrahan retitled this revision from [Probs needs changes] - tweak Maniphest and Application editors to handle policy corner cases better to Tweak application and maniphest editors to handle policy corner cases better.Mar 13 2014, 1:52 AM
btrahan updated this object.
btrahan edited the test plan for this revision. (Show Details)
epriestley edited edge metadata.

Yeah, this looks like we're on the right path to me. I think I caught at least some meat inline...

src/applications/maniphest/editor/ManiphestTransactionEditor.php
421

I think it's slightly more correct to just break; here, in the theoretical case that we have several TYPE_OWNER actions somehow (this "should" never happen, but who knows). I think the last one will end up winning, so using just a normal break; here will also let the last one win.

src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
899–900

I think this will prevent users who can, e.g., view a task but can not edit it from making comments. We should probably have a more sophisticated approach to this in the long run, where each transaction type defines whether it needs EDIT or just VIEW.

1522

Maybe rename this to $capability for clarity.

1552–1553

I would expect us to need/want a block here like:

if ($is_new) {
  if (!$xactions) {
    if (!PhabricatorPolicyFilter::hasCapability($actor, $policy_object, $policy)) {
      $errors[] = ...;
    }
  }
}

...to cover the "default is No One, and you didn't change it" case?

This revision now requires changes to proceed.Mar 13 2014, 1:56 AM

yah, that was some meat. :D

src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
899–900

gotchya. i'll take a stab at that in the update.

1552–1553

This is called *very* early. Things like filterTransactions haven't been called yet. As such, at least in Maniphest, !$xactions is always false and the correct behavior happens via the above loop instead. In the "default is no one, not assigned to you" case, the user sees

Validation errors:
- You can not select this view policy, because you would no longer be able to view the object.
- You can not select this edit policy, because you would no longer be able to edit the object.

I *think* though we always want to pass in $xactions like we do in Maniphest (and seem to for CustomFields) so maybe a better thing might be to 1) fix call sites in this diff and 2) make the check more like

if (!$xactions) {
  throw new Exception('You must be a baby or a puppy coder. Fix your code.');
}
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
1552–1553

Ah, that makes sense. A better example is maybe creating tasks via email: in that case, I think there will be no POLICY transactions. This is kind of a whole separate can of worms since you're kind of in trouble if the defaults don't let you see the objects, but you could imagine bin/lipsum or maniphest.createtask (which might not exist) also creating tasks without an explicit policy transaction in at least some cases.

I don't think we should require policy transactions in order to create objects, although throwing would also be OK. In either case it should be guarded by $is_new, though, since you definitely shouldn't need to add a policy transaction in order to add a comment.

So I think something in this form is probably reasonable, maybe?

if ($is_new) {
  if (!$xactions) {
    // Throw unconditionally, or do a policy check against the object as-is;
    // either one would get us where we want to be?
  }
}

If you throw, we should find all editors which create objects (Conduit, Lipsum, controllers) and make sure they always provide every POLICY transaction the object supports when creating an object. This seems easy to get wrong, so I sliiightly favor doing an explicit check.

btrahan edited edge metadata.
epriestley edited edge metadata.

Awesome, this covers everything I can think of. That test plan looks good to me too. Nice to get this buttoned up a bit.

src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
1564–1568

These probably aren't translatable in some languages, but that's a minor nit we can deal with when we get there.

This revision is now accepted and ready to land.Mar 13 2014, 8:48 PM
btrahan updated this revision to Diff 20214.

Closed by commit rPe6118bcbaf7d (authored by @btrahan).