Как защитить ресурсы пользователя в REST API с помощью FOSRestBundle и FOSOauthServerBundle?

Я разрабатываю веб-сервис RESTful в Symfony2 с пакетами FOSRest и FOSOauthServer (... и многими другими). Моя проблема в том, что с токеном доступа другого пользователя API дает ответ вместо кода состояния 403. Например:

У меня есть два пользователя, хранящиеся в базе данных

пользователь А с токеном А пользователь Б с токеном Б

Пример запроса http://example.com/api/v1/userA/products?access_token=tokenB

Текущий ответ

{
   products: {
    0: { ... } 
    1: { ... }
  }
}

Но я запрашиваю продукты пользователя A с токеном доступа пользователя B. Как я могу проверить, принадлежит ли предоставленный токен доступа владельцу продуктов??

Мой файл security.yml:

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] }

Мой файл routing.yml на ApiBundle

# API Endpoints
app_api_user_get_products:
    pattern: /{username}/products
    defaults: { _controller: ApiBundle:User:getProducts, _format: json }
    methods: GET

Мой 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);
    }
}

Огромное спасибо за помощь!


person xabi82    schedule 29.05.2014    source источник


Ответы (2)


Я нашел решение. Это легко, просто я добавил следующий код в свой контроллер отдыха и параметры конфигурации в 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();
        }
...

~/приложение/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
person xabi82    schedule 29.05.2014

Вы также можете сделать это проще, используя аннотацию @Security в Symfony >= 2.4 . В вашем случае это будет выглядеть

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

и заголовок действия:

...
public function getProductsAction(User $userWithProducts, ParamFetcherInterface $paramFetcher, Request $request) {
...
person piotr.jura    schedule 01.09.2014
comment
Спасибо за ответ, но мы пока используем только версию Symfony 2.3 (LTS) в производственных средах;) - person xabi82; 24.09.2014