Ver código fonte

Add the hash of the parsed content of the composer.json to the lock file, and use it to verify the json is not changed

Zsolt Szeberenyi 10 anos atrás
pai
commit
50b560fe4c

+ 15 - 1
src/Composer/Factory.php

@@ -306,7 +306,7 @@ class Factory
             $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
                 ? substr($composerFile, 0, -4).'lock'
                 : $composerFile . '.lock';
-            $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile));
+            $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile), $this->getContentHash($composerFile));
             $composer->setLocker($locker);
         }
 
@@ -485,4 +485,18 @@ class Factory
 
         return $factory->createComposer($io, $config, $disablePlugins);
     }
+
+    /**
+     * Returns the md5 hash of the sorted content of the composer file.
+     *
+     * @param string $composerFilePath Path to the composer file.
+     *
+     * @return string
+     */
+    private function getContentHash($composerFilePath)
+    {
+        $content = json_decode(file_get_contents($composerFilePath), true);
+        ksort($content);
+        return md5(json_encode($content));
+    }
 }

+ 10 - 1
src/Composer/Package/Locker.php

@@ -35,6 +35,7 @@ class Locker
     private $repositoryManager;
     private $installationManager;
     private $hash;
+    private $contentHash;
     private $loader;
     private $dumper;
     private $process;
@@ -48,13 +49,15 @@ class Locker
      * @param RepositoryManager   $repositoryManager   repository manager instance
      * @param InstallationManager $installationManager installation manager instance
      * @param string              $hash                unique hash of the current composer configuration
+     * @param string              $contentHash         unique hash of the content of the current composer configuration
      */
-    public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $hash)
+    public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $hash, $contentHash)
     {
         $this->lockFile = $lockFile;
         $this->repositoryManager = $repositoryManager;
         $this->installationManager = $installationManager;
         $this->hash = $hash;
+        $this->contentHash = $contentHash;
         $this->loader = new ArrayLoader(null, true);
         $this->dumper = new ArrayDumper();
         $this->process = new ProcessExecutor($io);
@@ -85,6 +88,11 @@ class Locker
     {
         $lock = $this->lockFile->read();
 
+        if (!empty($lock['content-hash'])) {
+            // There is a content hash key, use that instead of the file hash
+            return $this->contentHash == $lock['content-hash'];
+        }
+
         return $this->hash === $lock['hash'];
     }
 
@@ -241,6 +249,7 @@ class Locker
                                'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file',
                                'This file is @gener'.'ated automatically'),
             'hash' => $this->hash,
+            'content-hash' => $this->contentHash,
             'packages' => null,
             'packages-dev' => null,
             'aliases' => array(),

+ 3 - 1
tests/Composer/Test/InstallerTest.php

@@ -191,7 +191,8 @@ class InstallerTest extends TestCase
                 }));
         }
 
-        $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), md5(json_encode($composerConfig)));
+        $hash   = md5(json_encode($composerConfig));
+        $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $hash, $hash);
         $composer->setLocker($locker);
 
         $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
@@ -237,6 +238,7 @@ class InstallerTest extends TestCase
 
         if ($expectLock) {
             unset($actualLock['hash']);
+            unset($actualLock['content-hash']);
             unset($actualLock['_readme']);
             $this->assertEquals($expectLock, $actualLock);
         }

+ 40 - 7
tests/Composer/Test/Package/LockerTest.php

@@ -20,7 +20,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
     public function testIsLocked()
     {
         $json   = $this->createJsonFileMock();
-        $locker = new Locker(new NullIO, $json, $this->createRepositoryManagerMock(), $this->createInstallationManagerMock(), 'md5');
+        $locker = new Locker(new NullIO, $json, $this->createRepositoryManagerMock(), $this->createInstallationManagerMock(), 'md5', 'contentMd5');
 
         $json
             ->expects($this->any())
@@ -40,7 +40,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $repo = $this->createRepositoryManagerMock();
         $inst = $this->createInstallationManagerMock();
 
-        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5');
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
 
         $json
             ->expects($this->once())
@@ -58,7 +58,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $repo = $this->createRepositoryManagerMock();
         $inst = $this->createInstallationManagerMock();
 
-        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5');
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
 
         $json
             ->expects($this->once())
@@ -85,7 +85,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $repo = $this->createRepositoryManagerMock();
         $inst = $this->createInstallationManagerMock();
 
-        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5');
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
 
         $package1 = $this->createPackageMock();
         $package2 = $this->createPackageMock();
@@ -124,6 +124,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
                                    'Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file',
                                    'This file is @gener'.'ated automatically'),
                 'hash' => 'md5',
+                'content-hash' => 'contentMd5',
                 'packages' => array(
                     array('name' => 'pkg1', 'version' => '1.0.0-beta'),
                     array('name' => 'pkg2', 'version' => '0.1.10')
@@ -148,7 +149,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $repo = $this->createRepositoryManagerMock();
         $inst = $this->createInstallationManagerMock();
 
-        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5');
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'md5');
 
         $package1 = $this->createPackageMock();
         $package1
@@ -167,7 +168,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $repo = $this->createRepositoryManagerMock();
         $inst = $this->createInstallationManagerMock();
 
-        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5');
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
 
         $json
             ->expects($this->once())
@@ -183,7 +184,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $repo = $this->createRepositoryManagerMock();
         $inst = $this->createInstallationManagerMock();
 
-        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5');
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
 
         $json
             ->expects($this->once())
@@ -193,6 +194,38 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $this->assertFalse($locker->isFresh());
     }
 
+    public function testIsFreshWithContentHash()
+    {
+        $json = $this->createJsonFileMock();
+        $repo = $this->createRepositoryManagerMock();
+        $inst = $this->createInstallationManagerMock();
+
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
+
+        $json
+            ->expects($this->once())
+            ->method('read')
+            ->will($this->returnValue(array('hash' => 'oldMd5', 'content-hash' => 'contentMd5')));
+
+        $this->assertTrue($locker->isFresh());
+    }
+
+    public function testIsFreshFalseWithContentHash()
+    {
+        $json = $this->createJsonFileMock();
+        $repo = $this->createRepositoryManagerMock();
+        $inst = $this->createInstallationManagerMock();
+
+        $locker = new Locker(new NullIO, $json, $repo, $inst, 'md5', 'contentMd5');
+
+        $json
+            ->expects($this->once())
+            ->method('read')
+            ->will($this->returnValue(array('hash' => 'md5', 'content-hash' => 'oldMd5')));
+
+        $this->assertFalse($locker->isFresh());
+    }
+
     private function createJsonFileMock()
     {
         return $this->getMockBuilder('Composer\Json\JsonFile')