Skip to content

SubResource and security #1649

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

Closed
akazik opened this issue Jan 15, 2018 · 16 comments
Closed

SubResource and security #1649

akazik opened this issue Jan 15, 2018 · 16 comments

Comments

@akazik
Copy link

akazik commented Jan 15, 2018

I have entities A with one-to-many B, B is defined as a SubResource in A.
Both A and B have security checks which ensure that:

  • the collection can only be accessed by a admin
  • you can only access it if (the associated) A's id is your own

The following routs work as excepted (regular user):
/A (no access)
/A/id (only possible for own id)
/B (no access)
/B/id (only possible if A has your own id)

But the collection built by the SubResource is not restricted:
/A/id/B (can be accessed by everyone)

How can it be secured?

Code for security so far:

 * @ApiResource(
 *     attributes={
 *       "access_control"="is_granted('ROLE_USER')"
 *     },
 *     collectionOperations={
 *         "get"={"method"="GET", "access_control"="is_granted('ROLE_ADMIN')"},
 *         "post"={"method"="POST", "access_control"="is_granted('ROLE_ADMIN')"},
 *     },
 *     itemOperations={
 *         "get"={"method"="GET", "access_control"="is_granted('ROLE_USER') and object.getId() == user.getAccount().getId()"}
 *     }
 * )
@soyuka
Copy link
Member

soyuka commented Jan 15, 2018

Something might be missing here as the DenyAccessListener does take subresources into consideration:

$isGranted = $resourceMetadata->getCollectionOperationAttribute($attributes['subresource_operation_name'], 'access_control', null, true);

I think that you need to add a context for the subresource on the subresource operation name.
Might be linked to #1617 (where to declare subresource context).

@akazik
Copy link
Author

akazik commented Jan 15, 2018

I added
* "api_accounts_emails_get_subresource"={"method"="GET", "access_control"="is_granted('ROLE_USER') and object.getA().getId() == user.getAccount().getId()"},
to B's collectionOperations and it's executed, the problem is though that object is neither A nor B (but the Paginator) and thus I can't check if the objects belong to the user.

I would have preferred an check with A being the object since it will load something related to A.

Thanks for the reply, and a documentation about this would be also helpful.

Edit: I think I can work around it by creating a new expression language function which checks every item of the paginator.

@soyuka
Copy link
Member

soyuka commented Jan 15, 2018

Edit: I think I can work around it by creating a new expression language function which checks every item of the paginator.

Won't performances be slightly decreased by that? I think we had a recent issue about this and the paginator. Anyway I'd like this to be consistent with how the grant on Collection (that aren't part of a subresource) works.

/edit found #1481 ping @takeit

@dunglas
Copy link
Member

dunglas commented Jan 15, 2018

Won't performances be slightly decreased by that?

It will, and it's why it has been done this way initially. It may also break the pagination. If you want to check something for all rows, better use an extension to tweak the SQL query.

@dunglas
Copy link
Member

dunglas commented Jan 15, 2018

We may add a foreach helper function or something like this in ExpressionLanguage (with a big warning in the docs explaining when it should not be used).

@soyuka
Copy link
Member

soyuka commented Jan 15, 2018

We may add a foreach helper function or something like this in ExpressionLanguage (with a big warning in the docs explaining when it should not be used).

Meh shouldn't we enforce users to do proper querying instead? Even an additionnal query to check if the user has the given rights seems better then checking a whole collection to me.

@BatsaxIV
Copy link

Any news on this?
I have the same problem, it would nice to be able to access the subresource if the access is granted on the main ressource :

/A/id => Access granted
/A/id/B => also access granted (to avoid checking all subresources)

@matlar83
Copy link

Really can't understand why "object" is a Paginator instance.
This way is not possible to fine secure subresources in annotation like this:
"api_students_marks_get_subresource"={"method"="GET", "access_control"="is_granted('VIEW', object)", "path"="/students/{id}/all-marks"}

I think "object" should be a Student instance, instead.

How can I secure the subresource with a Paginator object?

@soyuka
Copy link
Member

soyuka commented Aug 21, 2018

For now, and because it is a complex issue if we don't want to introduce bad design I really suggest that you add an event listener (PRE_READ for example) that checks if the user has access to the request.

Yes, it's convenient to directly use the access_control but it's not that easy to implement on subresources and will definitely take time.

@matlar83
Copy link

matlar83 commented Aug 21, 2018 via email

@Lobosque
Copy link

@matlar83 I am going through the same process and I wonder if you were able to write your access rules in the PRE_READ event listener?
I wonder in cases where I get something like /api/albums/4/songs, how do I get a hold of the actual Album Entity with id 4 to do my security checks.

@Lobosque
Copy link

@soyuka also, a weird thing to note is that using PRE_READ, POST_READ, PRE_DESERIALIZE, POST_DESERIALIZE never call my subscriber function. only PRE_VALIDATE and POST_VALIDATE works. I thought these were supposed to be used on POST/PUT routes?

@mvkirk
Copy link

mvkirk commented Sep 4, 2018

PRE_READ and POST_READ work. The subscribe event is on the KernelEvents::REQUEST and not the KernelEvents::VIEW for those.

https://api-platform.com/docs/core/events

@mvkirk
Copy link

mvkirk commented Sep 4, 2018

Tell me if I did a mistake. In the access_control in addition of the object you also have access to variables in your path. For example /A/{id}/B. It's useful if the id variable depends on your user.

 *     attributes={
 *       "access_control"="is_granted('ROLE_USER')"
 *     },
 *     collectionOperations={
 *         "get"={"method"="GET", "access_control"="is_granted('ROLE_ADMIN')"},
 *         "post"={"method"="POST", "access_control"="is_granted('ROLE_ADMIN')"},
 *     },
 *     itemOperations={
 *         "get"={"method"="GET", "path"="/A/{id}/B", "access_control"="is_granted('ROLE_USER') and user.getAccount().getId() == id"}
 *     }
 * )

Otherwise if we need more complicated access control, we should use some events.

@juanwilde
Copy link

Maybe is too late but I did it by passing the id to the voter with this

"get"={"method"="GET", "path"="/users/{id}/expense_types", "access_control"="is_granted('USER_EXPENSE_TYPES_READ', id)"},

And then in the Voter

if (self::USER_EXPENSE_TYPES_READ === $attribute) {
      return $user->getId()->__toString() === $subject;
}

This is useful if the ID is a UUID object instead an integer, for example

@soyuka soyuka mentioned this issue Apr 7, 2019
4 tasks
@soyuka
Copy link
Member

soyuka commented Apr 7, 2019

Closing in favor of #2706

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants