Ver código fonte

Merge pull request #1177 from sandermarechal/stream-context

Allow setting stream context options
Jordi Boggiano 12 anos atrás
pai
commit
b3077bc4bc

+ 13 - 2
doc/04-schema.md

@@ -504,8 +504,10 @@ ignored.
 The following repository types are supported:
 
 * **composer:** A composer repository is simply a `packages.json` file served
-  via HTTP, that contains a list of `composer.json` objects with additional
-  `dist` and/or `source` information.
+  via the network (HTTP, FTP, SSH), that contains a list of `composer.json`
+  objects with additional `dist` and/or `source` information. The `packages.json`
+  file is loaded using a PHP stream. You can set extra options on that stream
+  using the `options` parameter.
 * **vcs:** The version control system repository can fetch packages from git,
   svn and hg repositories.
 * **pear:** With this you can import any pear repository into your composer
@@ -524,6 +526,15 @@ Example:
                 "type": "composer",
                 "url": "http://packages.example.com"
             },
+            {
+                "type": "composer",
+                "url": "https://packages.example.com",
+                "options": {
+                    "ssl": {
+                        "verify_peer": "true"
+                    }
+                }
+            },
             {
                 "type": "vcs",
                 "url": "https://github.com/Seldaek/monolog"

+ 7 - 0
doc/05-repositories.md

@@ -148,6 +148,13 @@ hash changed.
 This field is optional. You probably don't need it for your own custom
 repository.
 
+#### stream options
+
+The `packages.json` file is loaded using a PHP stream. You can set extra options
+on that stream using the `options` parameter. You can set any valid PHP stream
+context option. See [Context options and parameters](http://nl3.php.net/manual/en/context.php)
+for more information.
+
 ### VCS
 
 VCS stands for version control system. This includes versioning systems like

+ 40 - 0
doc/articles/handling-private-packages-with-satis.md

@@ -85,3 +85,43 @@ itself.
             "company/package3": "dev-master"
         }
     }
+
+### Security
+
+To secure your private repository you can host it over SSH or SSL using a client
+certificate. In your project you can use the `options` parameter to specify the
+connection options for the server.
+
+Example using a custom repository using SSH (requires the SSH2 PECL extension):
+
+    {
+        "repositories": [
+            {
+                "type": "composer",
+                "url": "ssh2.sftp://example.org",
+                "options": {
+                    "ssh2": {
+                        "username": "composer",
+                        "pubkey_file": "/home/composer/.ssh/id_rsa.pub",
+                        "privkey_file": "/home/composer/.ssh/id_rsa"
+                    }
+                }
+            }
+        ]
+    }
+
+Example using HTTP over SSL using a client certificate:
+
+    {
+        "repositories": [
+            {
+                "type": "composer",
+                "url": "https://example.org",
+                "options": {
+                    "ssl": {
+                        "cert_file": "/home/composer/.ssl/composer.pem",
+                    }
+                }
+            }
+        ]
+    }

+ 8 - 2
src/Composer/Repository/ComposerRepository.php

@@ -27,6 +27,7 @@ use Composer\Util\RemoteFilesystem;
 class ComposerRepository extends ArrayRepository implements NotifiableRepositoryInterface, StreamableRepositoryInterface
 {
     protected $config;
+    protected $options;
     protected $url;
     protected $io;
     protected $cache;
@@ -37,7 +38,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
 
     public function __construct(array $repoConfig, IOInterface $io, Config $config)
     {
-        if (!preg_match('{^\w+://}', $repoConfig['url'])) {
+        if (!preg_match('{^[\w.]+://}', $repoConfig['url'])) {
             // assume http as the default protocol
             $repoConfig['url'] = 'http://'.$repoConfig['url'];
         }
@@ -46,7 +47,12 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
             throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
         }
 
+        if (!isset($repoConfig['options'])) {
+            $repoConfig['options'] = array();
+        }
+
         $this->config = $config;
+        $this->options = $repoConfig['options'];
         $this->url = $repoConfig['url'];
         $this->io = $io;
         $this->cache = new Cache($io, $config->get('home').'/cache/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url));
@@ -199,7 +205,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
                 $jsonUrl = $this->url . '/packages.json';
             }
 
-            $json = new JsonFile($jsonUrl, new RemoteFilesystem($this->io));
+            $json = new JsonFile($jsonUrl, new RemoteFilesystem($this->io, $this->options));
             $data = $json->read();
 
             if (!empty($data['notify'])) {

+ 5 - 1
src/Composer/Util/RemoteFilesystem.php

@@ -30,15 +30,17 @@ class RemoteFilesystem
     private $result;
     private $progress;
     private $lastProgress;
+    private $options;
 
     /**
      * Constructor.
      *
      * @param IOInterface $io The IO instance
      */
-    public function __construct(IOInterface $io)
+    public function __construct(IOInterface $io, $options = array())
     {
         $this->io = $io;
+        $this->options = $options;
     }
 
     /**
@@ -241,6 +243,8 @@ class RemoteFilesystem
             $options['http']['header'] .= "Authorization: Basic $authStr\r\n";
         }
 
+        $options = array_replace_recursive($options, $this->options);
+
         return $options;
     }
 }

+ 19 - 2
tests/Composer/Test/Util/RemoteFilesystemTest.php

@@ -47,6 +47,23 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
         $this->assertContains('Authorization: Basic', $options['http']['header']);
     }
 
+    public function testGetOptionsForUrlWithStreamOptions()
+    {
+        $io = $this->getMock('Composer\IO\IOInterface');
+        $io
+            ->expects($this->once())
+            ->method('hasAuthorization')
+            ->will($this->returnValue(true))
+        ;
+
+        $streamOptions = array('ssl' => array(
+            'allow_self_signed' => true,
+        ));
+
+        $res = $this->callGetOptionsForUrl($io, array('https://example.org'), $streamOptions);
+        $this->assertTrue(isset($res['ssl']) && isset($res['ssl']['allow_self_signed']) && true === $res['ssl']['allow_self_signed'], 'getOptions must return an array with a allow_self_signed set to true');
+    }
+
     public function testCallbackGetFileSize()
     {
         $fs = new RemoteFilesystem($this->getMock('Composer\IO\IOInterface'));
@@ -102,9 +119,9 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
         unlink($file);
     }
 
-    protected function callGetOptionsForUrl($io, array $args = array())
+    protected function callGetOptionsForUrl($io, array $args = array(), array $options = array())
     {
-        $fs = new RemoteFilesystem($io);
+        $fs = new RemoteFilesystem($io, $options);
         $ref = new \ReflectionMethod($fs, 'getOptionsForUrl');
         $ref->setAccessible(true);