简体   繁体   中英

How to protect the resources of a user in a REST API with FOSRestBundle and FOSOauthServerBundle?

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

You can also make it simpler using @Security annotation in Symfony >= 2.4 . In your case it'll look like

/**
 * @Security("user.getId() == userWithProducts.getId()")
 */

and the action header:

...
public function getProductsAction(User $userWithProducts, ParamFetcherInterface $paramFetcher, Request $request) {
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM