Explorar o código

Support whitelist for xml document types

Michel Salib %!s(int64=12) %!d(string=hai) anos
pai
achega
6ac4d87ab2

+ 12 - 0
DependencyInjection/Configuration.php

@@ -158,6 +158,18 @@ class Configuration implements ConfigurationInterface
                             ->end()
                         ->end()
                     ->end()
+                    ->arrayNode('xml')
+                        ->addDefaultsIfNotSet()
+                        ->children()
+                            ->arrayNode('document_whitelist')
+                                ->beforeNormalization()
+                                    ->ifTrue(function($v){ return !is_array($v); })
+                                    ->then(function($v){ return array($v); })
+                                ->end()
+                                ->prototype('scalar')->end()
+                            ->end()
+                        ->end()
+                    ->end()
                 ->end()
             ->end()
         ;

+ 4 - 1
DependencyInjection/JMSSerializerExtension.php

@@ -21,7 +21,6 @@ namespace JMS\SerializerBundle\DependencyInjection;
 use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
 use Symfony\Component\HttpKernel\KernelInterface;
 use Symfony\Component\DependencyInjection\Alias;
-use Symfony\Component\DependencyInjection\DefinitionDecorator;
 use JMS\SerializerBundle\Exception\RuntimeException;
 use Symfony\Component\Config\FileLocator;
 use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@@ -149,6 +148,10 @@ class JMSSerializerExtension extends ConfigurableExtension
         $container
             ->setParameter('jms_serializer.json_serialization_visitor.options', $config['visitors']['json']['options'])
         ;
+
+        $container
+            ->setParameter('jms_serializer.xml_deserialization_visitor.document_whitelist', $config['visitors']['xml']['document_whitelist'])
+        ;
     }
 
     public function getConfiguration(array $config, ContainerBuilder $container)

+ 4 - 0
Resources/config/services.xml

@@ -33,6 +33,7 @@
         <parameter key="jms_serializer.json_deserialization_visitor.class">JMS\SerializerBundle\Serializer\JsonDeserializationVisitor</parameter>
         <parameter key="jms_serializer.xml_serialization_visitor.class">JMS\SerializerBundle\Serializer\XmlSerializationVisitor</parameter>
         <parameter key="jms_serializer.xml_deserialization_visitor.class">JMS\SerializerBundle\Serializer\XmlDeserializationVisitor</parameter>
+        <parameter key="jms_serializer.xml_deserialization_visitor.document_whitelist" type="collection"></parameter>
         <parameter key="jms_serializer.yaml_serialization_visitor.class">JMS\SerializerBundle\Serializer\YamlSerializationVisitor</parameter>
 
         <parameter key="jms_serializer.object_based_custom_handler.class">JMS\SerializerBundle\Serializer\Handler\ObjectBasedCustomHandler</parameter>
@@ -148,6 +149,9 @@
             <argument type="service" id="jms_serializer.naming_strategy" />
             <argument type="collection" /><!-- Custom Handlers -->
             <argument type="service" id="jms_serializer.object_constructor" />
+            <call method="setDocumentWhitelist">
+                <argument>%jms_serializer.xml_deserialization_visitor.document_whitelist%</argument>
+            </call>
             <tag name="jms_serializer.deserialization_visitor" format="xml" />
         </service>
         <service id="jms_serializer.yaml_serialization_visitor" class="%jms_serializer.yaml_serialization_visitor.class%" public="false">

+ 28 - 16
Resources/doc/configuration.rst

@@ -14,7 +14,7 @@ values:
 .. configuration-block ::
 
     .. code-block :: yaml
-    
+
         # config.yml
         jms_serializer:
             handlers:
@@ -25,17 +25,17 @@ values:
                 array_collection: true
                 form_error: true
                 constraint_violation: true
-    
+
             property_naming:
                 separator:  _
                 lower_case: true
-    
+
             metadata:
                 cache: file
                 debug: "%kernel.debug%"
                 file_cache:
                     dir: "%kernel.cache_dir%/serializer"
-    
+
                 # Using auto-detection, the mapping files for each bundle will be
                 # expected in the Resources/config/serializer directory.
                 #
@@ -43,7 +43,7 @@ values:
                 # class: My\FooBundle\Entity\User
                 # expected path: @MyFooBundle/Resources/config/serializer/Entity.User.(yml|xml|php)
                 auto_detection: true
-    
+
                 # if you don't want to use auto-detection, you can also define the
                 # namespace prefix and the corresponding directory explicitly
                 directories:
@@ -52,36 +52,41 @@ values:
                         path: "@MyFooBundle/Resources/config/serializer"
                     another-name:
                         namespace_prefix: "My\\BarBundle"
-                        path: "@MyBarBundle/Resources/config/serializer"    
+                        path: "@MyBarBundle/Resources/config/serializer"
+
+            visitors:
+                xml:
+                    document_whitelist:
+                        - '<!DOCTYPE authorized SYSTEM "http://some_url">' # an authorized document type for xml deserialization
 
     .. code-block :: xml
-    
+
         <!-- config.xml -->
         <jms-serializer>
             <handlers>
                 <object-based />
-                <datetime 
+                <datetime
                     format="Y-mdTH:i:s"
                     default-timezone="UTC" />
                 <array-collection />
                 <form-error />
-                <constraint-violation /> 
+                <constraint-violation />
             </handlers>
-            
+
             <property-naming
                 seperator="_"
                 lower-case="true" />
-                
+
             <metadata
                 cache="file"
                 debug="%kernel.debug%"
                 auto-detection="true">
-                
+
                 <file-cache dir="%kernel.cache_dir%/serializer" />
-                
+
                 <!-- If auto-detection is enabled, mapping files for each bundle will
-                     be expected in the Resources/config/serializer directory. 
-                     
+                     be expected in the Resources/config/serializer directory.
+
                      Example:
                      class: My\FooBundle\Entity\User
                      expected path: @MyFooBundle/Resources/config/serializer/Entity.User.(yml|xml|php)
@@ -90,5 +95,12 @@ values:
                     namespace-prefix="My\FooBundle"
                     path="@MyFooBundle/Resources/config/serializer" />
             </metadata>
+
+            <visitors>
+                <xml>
+                    <document_whitelist>
+                        <!DOCTYPE authorized SYSTEM "http://some_url">
+                    </document_whitelist>
+                </xml>
+            </visitors>
         </jms-serializer>
-    

+ 19 - 2
Serializer/XmlDeserializationVisitor.php

@@ -36,6 +36,7 @@ class XmlDeserializationVisitor extends AbstractDeserializationVisitor
     private $result;
     private $navigator;
     private $disableExternalEntities;
+    private $documentWhitelist = array();
 
     public function __construct(PropertyNamingStrategyInterface $namingStrategy, array $customHandlers, ObjectConstructorInterface $objectConstructor, $disableExternalEntities = true)
     {
@@ -67,7 +68,13 @@ class XmlDeserializationVisitor extends AbstractDeserializationVisitor
         $dom->loadXML($data);
         foreach ($dom->childNodes as $child) {
             if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
-                throw new \InvalidArgumentException('Document types are not allowed.');
+                $internalSubset = str_replace(PHP_EOL, '', $child->internalSubset);
+                if (!in_array($internalSubset, $this->documentWhitelist)) {
+                    throw new \InvalidArgumentException(sprintf(
+                        'The document type "%s" is not allowed. If it is safe, you may add it to the whitelist configuration.',
+                        $internalSubset
+                    ));
+                }
             }
         }
 
@@ -305,4 +312,14 @@ class XmlDeserializationVisitor extends AbstractDeserializationVisitor
     {
         return $this->result;
     }
-}
+
+    public function setDocumentWhitelist(array $documentWhitelist)
+    {
+        $this->documentWhitelist = $documentWhitelist;
+    }
+
+    public function getDocumentWhitelist()
+    {
+        return $this->documentWhitelist;
+    }
+}

+ 35 - 1
Tests/DependencyInjection/JMSSerializerExtensionTest.php

@@ -120,6 +120,40 @@ class JMSSerializerExtensionTest extends \PHPUnit_Framework_TestCase
         return $configs;
     }
 
+    /**
+     * @dataProvider getXmlVisitorWhitelists
+     */
+    public function testXmlVisitorOptions($expectedOptions, $config)
+    {
+        $container = $this->getContainerForConfig(array($config));
+        $this->assertSame($expectedOptions, $container->get('jms_serializer.xml_deserialization_visitor')->getDocumentWhitelist());
+    }
+
+    public function getXmlVisitorWhitelists()
+    {
+        $configs = array();
+
+        $configs[] = array(array('good document'), array(
+            'visitors' => array(
+                'xml' => array(
+                    'document_whitelist' => 'good document',
+                )
+            )
+        ));
+
+        $configs[] = array(array('good document', 'other good document'), array(
+            'visitors' => array(
+                'xml' => array(
+                    'document_whitelist' => array('good document', 'other good document'),
+                )
+            )
+        ));
+
+        $configs[] = array(array(), array());
+
+        return $configs;
+    }
+
     private function getContainerForConfig(array $configs, KernelInterface $kernel = null)
     {
         if (null === $kernel) {
@@ -154,4 +188,4 @@ class JMSSerializerExtensionTest extends \PHPUnit_Framework_TestCase
 
         return $container;
     }
-}
+}

+ 33 - 1
Tests/Serializer/XmlSerializationTest.php

@@ -18,7 +18,15 @@
 
 namespace JMS\SerializerBundle\Tests\Serializer;
 
+use Metadata\MetadataFactory;
+use Doctrine\Common\Annotations\AnnotationReader;
 use JMS\SerializerBundle\Tests\Fixtures\InvalidUsageOfXmlValue;
+use JMS\SerializerBundle\Serializer\Construction\UnserializeObjectConstructor;
+use JMS\SerializerBundle\Serializer\Naming\CamelCaseNamingStrategy;
+use JMS\SerializerBundle\Serializer\Naming\SerializedNameAnnotationStrategy;
+use JMS\SerializerBundle\Serializer\XmlDeserializationVisitor;
+use JMS\SerializerBundle\Metadata\Driver\AnnotationDriver;
+use JMS\SerializerBundle\Serializer\Serializer;
 use JMS\SerializerBundle\Exception\InvalidArgumentException;
 use JMS\SerializerBundle\Annotation\Type;
 use JMS\SerializerBundle\Annotation\XmlValue;
@@ -87,6 +95,30 @@ class XmlSerializationTest extends BaseSerializationTest
         $this->deserialize('<?xml version="1.0"?><!DOCTYPE foo><foo></foo>', 'stdClass');
     }
 
+    public function testWhitelistedDocumentTypesAreAllowed()
+    {
+        $xmlVisitor = new XmlDeserializationVisitor(
+            new SerializedNameAnnotationStrategy(new CamelCaseNamingStrategy()),
+            $this->getDeserializationHandlers(),
+            new UnserializeObjectConstructor()
+        );
+        $xmlVisitor->setDocumentWhitelist(array(
+            '<!DOCTYPE authorized SYSTEM "http://authorized_url.dtd">',
+            '<!DOCTYPE author [<!ENTITY foo SYSTEM "php://filter/read=convert.base64-encode/resource='.basename(__FILE__).'">]>'));
+
+        $serializer = new Serializer(new MetadataFactory(new AnnotationDriver(new AnnotationReader())), array(), array('xml' => $xmlVisitor));
+
+        $serializer->deserialize('<?xml version="1.0"?>
+            <!DOCTYPE authorized SYSTEM "http://authorized_url.dtd">
+            <foo></foo>', 'stdClass', 'xml');
+
+        $serializer->deserialize('<?xml version="1.0"?>
+            <!DOCTYPE author [
+                <!ENTITY foo SYSTEM "php://filter/read=convert.base64-encode/resource='.basename(__FILE__).'">
+            ]>
+            <foo></foo>', 'stdClass', 'xml');
+    }
+
     public function testVirtualAttributes() {
         $serializer = $this->getSerializer();
         $serializer->setGroups(array('attributes'));
@@ -130,4 +162,4 @@ class XmlSerializationTest extends BaseSerializationTest
     {
         return 'xml';
     }
-}
+}