FOSRestBundle icon indicating copy to clipboard operation
FOSRestBundle copied to clipboard

rule stop:true does not bypass FOSRestBundle

Open mpbzh opened this issue 9 years ago • 27 comments

I have a problem with a Symfony project where I have both FOSRestBundle and Sabre CardDav library. This is my FOSRestBundle configuration:

fos_rest:
    exception:
        enabled: true
        messages:
            'Symfony\Component\HttpKernel\Exception\HttpException': true
    param_fetcher_listener: true
    routing_loader:
        default_format: json
    view:
        view_response_listener: 'force'
        formats:
            xml:  true
            json: true
    allowed_methods_listener: true
    access_denied_listener:
        json: true

    format_listener:
        rules:
            - { path: '^/api', priorities: ['json'], fallback_format: json, }
            - { path: '^/', stop: true }

FOSRestBundle somehow breaks the CardDav functionality, even though the stop: true rule is added. It seems that stop: true does not completely bypass FOSRestBundle.

Or is there another way to reliably limit FOSRestBundle to a specific route?

mpbzh avatar Oct 01 '15 11:10 mpbzh

there might be something else breaking the CardDav functionality. to verify this, please disable the format_listener entirely and see if things work then.

lsmith77 avatar Oct 03 '15 11:10 lsmith77

Thanks! Do I disable it by just ommitting the format_listener or setting it to false?

mpbzh avatar Oct 03 '15 12:10 mpbzh

either way works

lsmith77 avatar Oct 04 '15 09:10 lsmith77

I did some further testing: Setting the format_listener to false did not have any effect, the CardDav functionality is still broken. Disabling the FOSRestBundle completely (Removing from the AppKernel.php and removing the config), however, restored full functionality.

The inital setup of a CardDav account (on the client) works, but not subsequent updates. Looking at the actual traffic caused by the client it might be related to the HTTP methods used: PROPFIND, OPTIONS and REPORT.

Other methods that the server accepts: OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT of which not all seem to be standard methods...

mpbzh avatar Oct 04 '15 15:10 mpbzh

After even further testing a I am now very confused, it works now as expected, even with FOSRestBundle activated.

I'm closing this issue until i notice any other problems.

mpbzh avatar Oct 04 '15 16:10 mpbzh

@mpbzh Hi, Which version of RestBundle are you using ? I have same problem. Disabling FOSRestBundle works only ( AppKernel, config and remove cache )

vcrproszek avatar Oct 06 '15 13:10 vcrproszek

So I did some in depth testing. It appears that the FOSRestBundle seems to be a problem after all.

Here's what I did:

  • I removed FOSRestBundle from the AppKernel.php and the config.yml and emptied the cache.
  • I monitored all packets that my phone exchanged with the server using an intercepting proxy (Burp).
  • I then set up the CardDav account on my phone. The initial pull of the address book worked fine, even if I reenabled FOSRest.
  • When I update the contact on the phone with FOSRest enabled, the error below happens. If FOSRest is disabled, it works fine (see below)
  • There is no difference in outcome when I deactivate the bundle completely vs. only setting format_listener: false

Request (redacted):

PUT /addressbooks/admin/list/contact-126.vcf HTTP/1.1
Host: [our server]
Content-Type: text/vcard; charset=utf-8
Accept-Encoding: gzip, deflate
Authorization: Basic [auth hash]
Connection: keep-alive
Accept: */*
User-Agent: iOS/9.1 (13B5110e) dataaccessd/1.0
If-Match: "32836d824bc54c955116fce22ef98ff6"
Content-Length: 309
Accept-Language: de-ch

BEGIN:VCARD
VERSION:3.0
PRODID:-//Apple Inc.//iOS 9.1//EN
N:Name;Full;;;
FN:Full Name
TITLE:Leiter IT
item2.TEL;type=pref:+410000000
REV:2015-10-07T08:09:46Z
UID:126
END:VCARD

Response without FOSRest (expected behavior):

HTTP/1.1 204 No Content
Date: Wed, 07 Oct 2015 08:23:02 GMT
Server: Apache
X-Sabre-Version: 2.1.3
ETag: "8ef1d7c566076d2d3ce88dfbbb4e2943"
Vary: Authorization
Content-Length: 0
X-Powered-By: PleskLin
Keep-Alive: timeout=5, max=10
Connection: Keep-Alive
Content-Type: text/html

Response with FOSRest (not working):

HTTP/1.1 415 Unsupported Media Type
Date: Wed, 07 Oct 2015 08:09:54 GMT
Server: Apache
X-Sabre-Version: 2.1.3
Vary: Authorization
X-Powered-By: PleskLin
Keep-Alive: timeout=5, max=10
Connection: Keep-Alive
Content-Type: application/xml; charset=utf-8
Content-Length: 2996

<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
  <s:sabredav-version>2.1.3</s:sabredav-version>
  <s:exception>Sabre\DAV\Exception\UnsupportedMediaType</s:exception>
  <s:message>This resource only supports valid vCard or jCard data. Parse error: End of document reached prematurely</s:message>
  <s:file>/home/httpd/vhosts/mydomain/vendor/sabre/dav/lib/CardDAV/Plugin.php</s:file>
  <s:line>429</s:line>
  <s:code>0</s:code>
  <s:stacktrace>#0 /home/httpd/vhosts/mydomain/vendor/sabre/dav/lib/CardDAV/Plugin.php(362): Sabre\CardDAV\Plugin-&gt;validateVCard('', false)
#1 [internal function]: Sabre\CardDAV\Plugin-&gt;beforeWriteContent('addressbooks/ad...', Object(Sabre\CardDAV\Card), '', false)
#2 /home/httpd/vhosts/mydomain/vendor/sabre/event/lib/EventEmitterTrait.php(105): call_user_func_array(Array, Array)
#3 /home/httpd/vhosts/mydomain/vendor/sabre/dav/lib/DAV/Server.php(1085): Sabre\Event\EventEmitter-&gt;emit('beforeWriteCont...', Array)
#4 /home/httpd/vhosts/mydomain/vendor/sabre/dav/lib/DAV/CorePlugin.php(501): Sabre\DAV\Server-&gt;updateFile('addressbooks/ad...', Resource id #390, NULL)
#5 [internal function]: Sabre\DAV\CorePlugin-&gt;httpPut(Object(Sabre\HTTP\Request), Object(Sabre\HTTP\Response))
#6 /home/httpd/vhosts/mydomain/vendor/sabre/event/lib/EventEmitterTrait.php(105): call_user_func_array(Array, Array)
#7 /home/httpd/vhosts/mydomain/vendor/sabre/dav/lib/DAV/Server.php(469): Sabre\Event\EventEmitter-&gt;emit('method:PUT', Array)
#8 /home/httpd/vhosts/mydomain/vendor/sabre/dav/lib/DAV/Server.php(254): Sabre\DAV\Server-&gt;invokeMethod(Object(Sabre\HTTP\Request), Object(Sabre\HTTP\Response))
#9 /home/httpd/vhosts/mydomain/src/MyBundle/Controller/MasterData/CardDavController.php(71): Sabre\DAV\Server-&gt;exec()
#10 [internal function]: MyBundle\Controller\MasterData\CardDavController-&gt;indexAction()
#11 /home/httpd/vhosts/mydomain/app/bootstrap.php.cache(3054): call_user_func_array(Array, Array)
#12 /home/httpd/vhosts/mydomain/app/bootstrap.php.cache(3016): Symfony\Component\HttpKernel\HttpKernel-&gt;handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#13 /home/httpd/vhosts/mydomain/app/bootstrap.php.cache(3165): Symfony\Component\HttpKernel\HttpKernel-&gt;handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#14 /home/httpd/vhosts/mydomain/app/bootstrap.php.cache(2406): Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel-&gt;handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#15 /home/httpd/vhosts/mydomain/web/app.php(28): Symfony\Component\HttpKernel\Kernel-&gt;handle(Object(Symfony\Component\HttpFoundation\Request))
#16 {main}</s:stacktrace>
</d:error>

Note: I am still using the same FOSRest configuration as posted above.

mpbzh avatar Oct 07 '15 08:10 mpbzh

if you could make a fork and a reproducible test case inside for example https://github.com/gimler/symfony-rest-edition, then I could debug this myself a bit.

lsmith77 avatar Oct 07 '15 18:10 lsmith77

This fork shows the issue described above: https://github.com/krzysztofbitnoise/symfony-rest-edition

Thanks for having a look at it!

mpbzh avatar Oct 12 '15 15:10 mpbzh

what urls should I call there to see the issue?

lsmith77 avatar Oct 16 '15 16:10 lsmith77

BTW I figured out that I can use mod:1234 for the login .. but beyond that I am not sure how to get any useful output .. that being said .. it looks like a proper carddav response though:

lsmith@pooteeweet krzysztofbitnoise (2.7)$ http http://krzysztofbitnoise.lo/app_dev.php/mod --auth mod:1234HTTP/1.1 404 Not Found
Content-Length: 270
Content-Type: application/xml; charset=utf-8
Date: Fri, 16 Oct 2015 17:23:06 GMT
Server: Apache/2.4.16 (Unix) PHP/5.6.14
X-Powered-By: PHP/5.6.14
X-Sabre-Version: 2.1.7

<?xml version="1.0" ?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">


    <s:sabredav-version>2.1.7</s:sabredav-version>


    <s:exception>Sabre\DAV\Exception\NotFound</s:exception>


    <s:message>File not found: mod in 'root'</s:message>


</d:error>

lsmith77 avatar Oct 16 '15 17:10 lsmith77

To see the behavior above you best configure the server as a new CardDav account on your smartphone. iPhones support this natively, Android phones need a (free) app installed.

You should then let the phone sync the contacts, open one, change any value and save. Have a look at the server response (which is an exception in case of FOSRest enabled).

If you don't want to configure your phone you can find out the URL of a specific contact and then send a PUT request to the server, like the one listed above.

As far as my testing goes, I have only seen PUT requests being affected by the issue.

mpbzh avatar Oct 19 '15 07:10 mpbzh

@mpbzh that is the thing .. what is the url to a specific contact? the library makes it a bit non obvious where URL parameters are read and how they are used in queries.

lsmith77 avatar Oct 19 '15 07:10 lsmith77

I see. If you set the parameter carddav_browser in your parameters.yml to true, you have a rendered representation of the structure when accessing the server with the browser.

The direct URL to a contact would be /addressbooks/mod/list-of-example-contacts/contact-1.vcf

mpbzh avatar Oct 19 '15 07:10 mpbzh

lsmith@pooteeweet krzysztofbitnoise (2.7)$ git diff app/config/
diff --git a/app/config/config.yml b/app/config/config.yml
index 6a6306e..9742888 100644
--- a/app/config/config.yml
+++ b/app/config/config.yml
@@ -51,7 +51,7 @@ doctrine:
         charset:  UTF8
         # if using pdo_sqlite as your database driver, add the path in parameters.yml
         # e.g. database_path: %kernel.root_dir%/data/data.db3
-        # path:     %database_path%
+        path:     %database_path%

     orm:
         auto_generate_proxy_classes: %kernel.debug%
@@ -79,7 +79,8 @@ fos_rest:
             html: true
     format_listener:
         rules:
-            - { path: ^/, priorities: [ html, json, xml ], fallback_format: ~, prefer_extension: true }
+            - { path: '^/api', priorities: ['json'], fallback_format: json, }
+            - { path: '^/', stop: true }
         media_type:
             version_regex: '/(v|version)=(?P<version>[0-9\.]+)/'
     exception:
lsmith@pooteeweet krzysztofbitnoise (2.7)$ http http://krzysztofbitnoise.lo/app_dev.php//addressbooks/mod/list-of-example-contacts/contact-1.vcf --auth mod:1234
HTTP/1.1 200 OK
Content-Length: 444
Content-Type: text/vcard; charset=utf-8
Date: Mon, 19 Oct 2015 07:44:46 GMT
ETag: "145ca716db1bc9810e3085ca87aa5950"
Last-Modified: Mon, 19 Oct 2015 07:44:47 GMT
Server: Apache/2.4.16 (Unix) PHP/5.6.14
X-Powered-By: PHP/5.6.14
X-Sabre-Version: 2.1.7

BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject 3.4.7//EN
FN:Roy Kilback
TEL:(533)739-3887x23601
N:Kilback;Roy;;;
EMAIL:[email protected]
UID:1
TEL;TYPE=CELL:1-873-387-1562
ADR;TYPE=work:920 Reinger Trace Apt. 283;;;;;;
NOTE:Voluptatibus rerum cupiditate at repudiandae asperiores aut. Ratione q
 ui et excepturi voluptatem assumenda a. Ut voluptas error voluptatibus et
 eos earum necessitatibus.
END:VCARD

lsmith77 avatar Oct 19 '15 07:10 lsmith77

Looks good. Now you need to send a PUT request with the same body, but change for example the email to something else. That should lead to the same exception as above

mpbzh avatar Oct 19 '15 07:10 mpbzh

sorry it took me so long to get back to you:

when I do the following

lsmith@pooteeweet krzysztofbitnoise (2.7)*$ http PUT http://krzysztofbitnoise.lo/app_dev.php/addressbooks/mod/list-of-example-contacts/contact-1.vcf Content-Type:text/vcard --auth=mod:1234 < contact.vcf
HTTP/1.1 204 No Content
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Sun, 25 Oct 2015 13:01:40 GMT
ETag: "16d502e52c97526e09b4d7036f9977d2"
Server: Apache/2.4.16 (Unix) PHP/5.6.14
X-Powered-By: PHP/5.6.14
X-Sabre-Version: 2.1.7

I see that the CardDavController is called and if I dump $request->getContent() in the controller action, I see the modified data as expected. However it doesn't seem to be stored.

lsmith77 avatar Oct 25 '15 13:10 lsmith77

@mpbzh note that I send the PUT with Content-Type:text/vcard

lsmith77 avatar Oct 25 '15 13:10 lsmith77

@lsmith77 Thanks for looking into it. Have you checked if the Sabre\DAV\Exception\UnsupportedMediaType exeption mentioned in a comment above is also thrown? That would explain why nothing is stored and would lead to an assumption that FOSRest somehow tampers with the request (as this only happens if FOSRest is enabled)

mpbzh avatar Oct 27 '15 11:10 mpbzh

Sorry for hijacking but I seem to have very similar issues with symfony2 custom error pages. Only way to make it work is to set:

fos_rest:
    body_converter:
        enabled: false

But I need the body converter to work.

The testing routes can be enabled by setting this to routing_dev.yml:

_errors:
    resource: "@TwigBundle/Resources/config/routing/errors.xml"
    prefix:   /_error

And just accessing http://127.0.0.1/_error/404 I get these exceptions: https://gist.github.com/paali/7e2dc8c00a2d989e0298

In production environment the error pages won't work but I cannot figure out why from the short exception trace sent through email. Maybe the same issue or maybe not.

paali avatar Nov 12 '15 09:11 paali

@paali I just stumbled upon the same problem. I need the body_converter in one place, but now it wants to convert every Object inside my action signature...

temp avatar Feb 03 '16 12:02 temp

@temp a solution is to decorate the service and change its tag with a lower priority.

@lsmith77 I think we should do that in 2.0.

GuilhemN avatar Feb 03 '16 12:02 GuilhemN

I always thought that a @ParamConverter annotation is needed for this. Never used them much, though.

temp avatar Feb 03 '16 13:02 temp

@paali this issue seems to be covered in https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1237 ?

aside from this .. in 2.0 its possible to disable the entire events based on routes .. the question is therefore if we should even keep the "stop" rule in 2.0 or remove it entirely?

lsmith77 avatar Feb 08 '16 14:02 lsmith77

@lsmith77 I guess we should keep it for now as we can't disable a specific listener.

@paali this was fixed in https://github.com/FriendsOfSymfony/FOSRestBundle/pull/1346

GuilhemN avatar Mar 12 '16 18:03 GuilhemN

Maybe we can extend the zone concept in 2.1 to make it possible to only disable some of the listeners. Then we could indeed entirely remove the stop rule. What do you think?

xabbuh avatar Mar 15 '16 08:03 xabbuh

@temp @paali The FOSRestBundle body converter requires the SensioFrameworkExtraBundle request.converters setting to be true (see https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html ).

sensio_framework_extra:
    request: { converters: true }

The default behavior of this setting is to automatically convert arguments that are type hinted in the controller action signature. You can disable this:

sensio_framework_extra:
    request: { converters: true, auto_convert: false }

For me setting this to false solved a similar issue as @paali and @temp : I have FOSRestBundle combined with NelmioApiDocBundle. I use the body converter for converting json objects that are POSTed. The documentation of some routes can only be seen when logged in (via form login). The api is at / The docs are at /doc Form login for docs at /doc/login

Before disabling the auto_convert setting GET /doc/login gave following error:

The format "" is not supported for deserialization.

Inwerpsel avatar Jul 26 '17 08:07 Inwerpsel