Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EZP-29474: Fix wrong usage of hasAccess in repository #2413

Merged
merged 8 commits into from Sep 10, 2018
Merged

Conversation

andrerom
Copy link
Contributor

@andrerom andrerom commented Aug 10, 2018

Question Answer
JIRA issue EZP-29474
Bug/Improvement yes
New feature no
Target version 6.7
BC breaks yes (2, see below)
Tests pass yes
Doc needed afaik no

Fix wrong usage* of hasAccess() in repository in favor of canUser() in order to solve:

  • Issues when there are Role Assignment Limitation, as in this case they will return info to permission system they abstain from "voting"
  • As policies are now extendable, make code prepared in case someone needs to customize and add limitations to any of these functions, or we end up adding it out of the box ourselves.

In effect this makes Repo more correctly use permissions system, and will avoid quite some unneeded edge case exceptions all across the repo on these cases, the issue reported by QA is afaik just scratching the surface here (see diff).

There is also two behavior changes (in terms of api spec) which will simplify API usage:

  • Done in PR: loadSections(): PR proposes to change this to just filter sections (preparing it for we/others adding policy limitations on the function, & avoiding issue with role limitation)
  • Proposed in PR in inline comment: load user drafts: Same, proposes to filter the returned drafts instead of throwing

* Long story short, we forgot about Role limitations + extensibility when coding this, and made shortcuts hard coding that assumption, this PR fixes that.

What about #2408?

Hopefully this solves the bug, meaning the proposed changes in #2408 will hopefully get some more time to mature as a separate (new) feature/Story. As it basically changes the behavior to not follow API spec anymore, which is indication that it needs some more baking and probably we should rather design a new api method for cases where we don't have an object, as the code involved at least would need to know what kind of object (interface FQN for instance) was expected to be able to tell the permission system to safely ignore a given limitation or not (for instance abstain feature of role limitations could be extracted into new role limitations type interface as a start to try to better separate between policy and role limitation responsibilities, and can be used to check this without having object, ping me if unclear I can also do a POC on it for discussion).

TODO:

  • Implement feature / fix a bug.
  • Implement tests.
  • Fix new code according to Coding Standards ($ composer fix-cs).
  • Ask for Code Review.
  • Open question maybe more for a follow PR: Should we deprecate hasAccess given it can be miss-leading? (aka people only expecting it to return boolean, while it really is designed to give you a yes/no/maybe anwear, in the latter case the system needs more context basically, but besides canUser other use case have not been solved in API for this like whatCotnentTypeCanUserCreateHere(Location)) So.. what should we call it? And what should it return? What do you need?

@ezrobot

This comment has been minimized.

@@ -1103,6 +1103,7 @@ public function loadContentDrafts(User $user = null)
}

// throw early if user has absolutely no access to versionread

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CS

@@ -1111,6 +1112,7 @@ public function loadContentDrafts(User $user = null)
$versionInfoList = array();
foreach ($spiVersionInfoList as $spiVersionInfo) {
$versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
// TODO: Should we rather filter out items here? If so we can remove early permission check above and exception
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think this would be more reasonable.

// REVIEW NOTE: NewSection limitation expects target to be the section, while Role Subtree limitations will abstain from checking
// due to target being section. We could consider changing Subtree to load targets from content and check anyway. So
// we make sure section assignments are limited by subtree limitations which could make sense given object is content.
if (!$this->permissionResolver->canUser('section', 'assign', $loadedContentInfo, [$section])) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question 1:

REVIEW NOTE: NewSection limitation expects target to be the section, while Role Subtree limitations will abstain from checking due to target being section.

So you're saying that if we have Subtree limitation, here we can assign section to content which belongs to another subtree? :)

Question 2:
any reason you've replaced $loadedSection with $section in this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason you've replaced $loadedSection with $section in this line?

no, that is a "mistake" and I'll change it back to use `$loadedSection . Even if I perosnally don't think we need to re-load things like this in the first place ;)

Copy link
Contributor Author

@andrerom andrerom Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you're saying that if we have Subtree limitation, here we can assign section to content which belongs to another subtree? :)

I'm saying both NewSection and SubtreeLimitation (role limitation) could be adhered to.

I can open that as separate PR to master so we can take that as separate thing?

Copy link
Member

@alongosz alongosz Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm saying both NewSection and SubtreeLimitation (role limitation) could be adhered to.
I can open that as separate PR to master so we can take that as separate thing?

Yes please :)

Copy link
Member

@alongosz alongosz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, looks okay-ish ;)
Let's make sure that we don't change the behavior for 6.7.

So while I'm in favor of filtering versions when loading drafts for user, it feels like, if you'll go that direction, it should be targeted to master.
The same goes for Trash operations, but on that one - maybe we just need to check if behavior changed.

The other changes - this fixes existing bugs, so even if something changed, it needs to, so +1

@@ -1111,6 +1112,7 @@ public function loadContentDrafts(User $user = null)
$versionInfoList = array();
foreach ($spiVersionInfoList as $spiVersionInfo) {
$versionInfo = $this->domainMapper->buildVersionInfoDomainObject($spiVersionInfo);
// TODO: Should we rather filter out items here? If so we can remove early permission check above and exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think this would be more reasonable.

// REVIEW NOTE: NewSection limitation expects target to be the section, while Role Subtree limitations will abstain from checking
// due to target being section. We could consider changing Subtree to load targets from content and check anyway. So
// we make sure section assignments are limited by subtree limitations which could make sense given object is content.
if (!$this->permissionResolver->canUser('section', 'assign', $loadedContentInfo, [$section])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question 1:

REVIEW NOTE: NewSection limitation expects target to be the section, while Role Subtree limitations will abstain from checking due to target being section.

So you're saying that if we have Subtree limitation, here we can assign section to content which belongs to another subtree? :)

Question 2:
any reason you've replaced $loadedSection with $section in this line?

$spiTrashItem = $this->persistenceHandler->trashHandler()->loadTrashItem($trashItemId);
$trash = $this->buildDomainTrashItemObject($spiTrashItem);
if (!$this->repository->canUser('content', 'read', $trash->getContentInfo())) {
throw new UnauthorizedException('content', 'read');
}

// TODO: Need to check (integration tests + self + QA) how Role limitation will behave with this (as there is no location)
// we could pass trash as target (same goes for content/read above), but again would need to check how it will behave.
if (!$this->repository->canUser('content', 'restore', $trash->getContentInfo())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. There is a risk this will fail with subtree limitation on Role. Trash item after all doesn't belong to subtree it was removed from...

if (!$this->repository->canUser(
'content',
'restore',
$trashItem->getContentInfo(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same comment as above, but I guess we just need to check this when moved to QA.

Copy link
Contributor Author

@andrerom andrerom Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this one needs to be tested, the change implies role limitation will kick in here only allowing you to restore within trees you have been granted role with restore rights.

Which I think makes sense here as we have a proper location which is a target of the restore action (both in case different location was provided and in case where trash location is to be the target)

As for "above", this is not the case anymore after PR updates for checks on read permission:

  1. It does now not include the trash location as target there (as it is about checking if you can read content in trash, location is irrelevant)
  2. PR now adapts SubtreeLimitationType to abstain if no target is provided and content is in trash to make this work.

@andrerom
Copy link
Contributor Author

andrerom commented Aug 23, 2018

Ready for review, followup PR with some earlier suggested changes to behavior will come later for master / @alongosz @adamwojs

@andrerom andrerom changed the title EZP-29474: Fix wrong usage of hasAccess in repopsitory EZP-29474: Fix wrong usage of hasAccess in repository Aug 24, 2018
@@ -33,6 +33,9 @@ class SectionService implements SectionServiceInterface
*/
protected $repository;

/** @var \eZ\Publish\API\Repository\PermissionResolver */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrerom: nitpick - we've decided (with @lserwatka's approval) that we're not going to use single line type hinting for class members.

Reasons:

  • if you want to add more info for a member, you'll need to make it multiline anyway, so this is inconsistent (on a very abstract/weird level of thinking, to me, this is the same as returning either array or value depending on needs, which is bad ;) )
  • there are far more multiline type-hints (3095) than single line (278) across the code base.
  • GitHub doesn't get it (doesn't highlight it properly).

We rather use single line inside methods only, to type-hint specific variables, which have unknow type - there it makes sense to me. Of course only if we have to (the direction is rather to use strict types to avoid that, but there are places we cannot)

Copy link
Contributor Author

@andrerom andrerom Aug 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, lets hope typed properties arrive in PHP 7.4 as intended so at some point you can rid of these completely then ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 15529ab

@@ -53,6 +58,7 @@ class SectionService implements SectionServiceInterface
public function __construct(RepositoryInterface $repository, Handler $sectionHandler, array $settings = array())
{
$this->repository = $repository;
$this->permissionResolver = $repository->getPermissionResolver();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather just a question regarding these changes. In all other services $this->repository->canUser() is used, is SectionService different somehow and needs to has an instance of PermissionResolver?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kmadejski

* @deprecated since 6.6, to be removed. Use PermissionResolver::canUser() instead.

Copy link
Member

@kmadejski kmadejski Aug 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wizhippo thanks! I know about that deprecation, this is exactly why I've asked this question. As you can see, even after changes from this PR all other services still use deprecated $this->repository->canUser(). Therefore, I'm just curious why this has been changed only for SectionService.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Good question.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kmadejski because it feels a bit out of the scope of this PR. Too many changes slow down review and approval.
But you are more than welcomed to improve our code which still uses Repository::canUser directly 😀

Copy link
Contributor Author

@andrerom andrerom Aug 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yeah, I did it on this one only as here it was not to invasive, but in general it's a small step we could do all over to help us be able to de-couple this. As it makes it more clear what is using repo as this is the number one thing we want to get rid of (cyclic dependency). In order to set these services and their helpers up using clear dependency injection instead of being initiated in Repo inline.

Fix wrong usage\* of hasAccess() in repopsitory in favour of canUser in order to solve:
- Issues when there are Role Assignment Limitation
- As policies are now extendable, better be prepared in case someone needs to cusotmize and add limitations to any of these functions

I effect this makes Repo more correclty use permissions sytem, and will avoid quite some unneeded exceptions all across the repo.

There is also two behavour changes _(in terms of api spec)_ which will simplify API usage:
- loadSections(): PR proposes to change this to just filter sections _(preparing it for we/others adding policy limitations on the functon, & avoiding issue with role limitation)_
- load user drafts: Same, proposes to filter the returned drafts instead of throwing

\* _Long story short, we forgot about Role limitations + extensibility when coding this, and made shortcuts hard codeing that assumiption, this PR fixes that._
Also fix CS issue on test.

[skip ci]
@andrerom
Copy link
Contributor Author

@vidarl Could you check why CS checked does not run?
Because of it, we end up with CS issues in branches: 28169c3

@alongosz did you consider adding cs check on travis here like on some other reposP

@andrerom andrerom merged commit 8ad7759 into 6.7 Sep 10, 2018
@andrerom andrerom deleted the repo_use_canUser branch September 10, 2018 02:00
@alongosz
Copy link
Member

did you consider adding cs check on travis here like on some other repos

@andrerom Well, for kernel it requires some performance improvements, because what we have on Travis, currently it runs for all files AFAIR. Here we have 3400 of them, so... :) I'll look into this in some spare time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
6 participants