I'm developing a RESTful web service in Symfony2 with FOSRest and FOSOauthServer bundles (... and many others). My problem is that with an access token of other user, the api gives response instead of a 403 status code. For example:
I have two users stored on database
userA with tokenA userB with tokenB
Example Request http://example.com/api/v1/userA/products?access_token=tokenB
Current Response
{
products: {
0: { ... }
1: { ... }
}
}
But I'm requesting products of user A with an access token of user B. How could I check if access token provided is of the products' owner??
My security.yml file:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
MY_ROLE:
# ...
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
SONATA:
- ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
anonymous: true
# FOSOAuthBundle and FOSRestBundle
oauth_token:
pattern: ^/oauth/v2/token
security: false
# oauth_authorize: commented because there are not oauth login form on this app
# pattern: ^/oauth/v2/auth
# Add your favorite authentication process here
api:
pattern: ^/api
fos_oauth: true
stateless: true
anonymous: false
# This firewall is used to handle the public login area
# This part is handled by the FOS User Bundle
main:
# ...
access_control:
# ...
# API (FOSRestBundle and FOSOAuthBundle)
- { path: ^/api, roles: [IS_AUTHENTICATED_FULLY] }
My routing.yml on ApiBundle
# API Endpoints
app_api_user_get_products:
pattern: /{username}/products
defaults: { _controller: ApiBundle:User:getProducts, _format: json }
methods: GET
My UserController.php
<?php
namespace App\ApiBundle\Controller;
Use App\MainBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ... more use statments
class UserController extends ApiController {
/**
* List user's products.
*
* @ApiDoc(
* resource = true,
* description="This method must have the access_token parameter. The parameters limit and offset are optional.",
* filters={
* {"name"="access_token", "dataType"="string", "required"="true"},
* {"name"="offset", "dataType"="integer", "default"="0", "required"="false"},
* {"name"="limit", "dataType"="integer", "default"="250", "required"="false"}
* },
* )
*
* @Annotations\QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing products.")
* @Annotations\QueryParam(name="limit", requirements="\d+", default="500", description="How many products to return.")
*
* @Annotations\View()
*
* @param User $user the request object
* @param ParamFetcherInterface $paramFetcher param fetcher service
*
* @return array
*/
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {
// $offset = $paramFetcher->get('offset');
// $offset = null == $offset ? 0 : $offset;
// $limit = $paramFetcher->get('limit');
try {
// estructure and exclude fields strategy http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies
$data = array('products' => array());
foreach ($user->getCatalog() as $p) {
if ($p->getAvailable() == true) {
$product = $p->getProduct();
$data['products'][] = array(
'id' => $product->getId(),
'reference' => $product->getReference(),
'brand' => $product->getBrand(),
'description' => $product->getDescription(),
// ...
);
}
}
} catch (\Exception $e) {
throw new HttpException(Codes::HTTP_INTERNAL_SERVER_ERROR, $e->getTraceAsString());
}
// New view
$view = new View($data);
$view->setFormat('json');
return $this->handleView($view);
}
}
Thank you very much for the help!
I've found the solution. It's easy, just I've added the following code in my rest controller and the configuration parameters on app/config.yml
UserController.php
...
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {
// Check if the access_token belongs to the user making the request
$requestingUser = $this->get('security.context')->getToken()->getUser();
if (!$requestingUser || $requestingUser !== $user) {
throw new AccessDeniedHttpException();
}
...
~/app/config.yml
# FOSRestBundle
fos_rest:
routing_loader:
default_format: json
param_fetcher_listener: true
view:
view_response_listener: force
access_denied_listener: # I've added this
# all requests using the 'json' format will return a 403 on an access denied violation
json: true