diff --git a/doc/04-schema.md b/doc/04-schema.md index 5ba680bd8..8f619ee3a 100644 --- a/doc/04-schema.md +++ b/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" diff --git a/doc/05-repositories.md b/doc/05-repositories.md index c0f70c1e5..a675ab47c 100644 --- a/doc/05-repositories.md +++ b/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 diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index 722a497ad..e7fa23509 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/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", + } + } + } + ] + } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 51c98dec0..5904e6a94 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/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'])) { diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index e82313033..3ccb127ac 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/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; } } diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index 4824e1af9..4dd3aa0e7 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/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);