Skip to content

Commit

Permalink
Allow add the multiple ssh keys from UI for different git repositories
Browse files Browse the repository at this point in the history
- refactor create composer config logic
- remove validation logic from entity class
- small fixes
  • Loading branch information
vtsykun committed Nov 23, 2019
1 parent 2cc0a12 commit ace7300
Show file tree
Hide file tree
Showing 40 changed files with 1,228 additions and 415 deletions.
2 changes: 1 addition & 1 deletion app/config/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ security:

firewalls:
packages:
pattern: (^(/packages.json$|/p/|/zipball/|/feeds/.+(\.rss|\.atom)|/packages/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?\.json|/packages/list\.json|/downloads/|/api/))+
pattern: (^(/packages.json$|/p/|/zipball/|/feeds/.+(\.rss|\.atom)|/packages/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?(\.json|/changelog)|/packages/list\.json|/downloads/|/api/))+
api_basic: true

main:
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"exclude-from-classmap": [ "src/Packagist/WebBundle/Tests/" ]
},
"require": {
"php": ">=7.0",
"php": ">=7.1",
"symfony/symfony": "^3.4",
"doctrine/orm": "^2.6",
"doctrine/doctrine-bundle": "^1.2",
Expand All @@ -45,7 +45,7 @@
"jms/security-extra-bundle": "^1.5",
"jms/di-extra-bundle": "^1.4",
"oro/doctrine-extensions": "^1.2",
"composer/composer": "^1.6",
"composer/composer": "^1.9",
"friendsofsymfony/user-bundle": "^2.1",
"predis/predis": "^1.0",
"snc/redis-bundle": "^2.0",
Expand Down
73 changes: 73 additions & 0 deletions src/Packagist/WebBundle/Composer/PackagistFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace Packagist\WebBundle\Composer;

use Composer\Config;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Packagist\WebBundle\Composer\Util\ProcessExecutor;
use Packagist\WebBundle\Entity\SshCredentials;

class PackagistFactory
{
protected $tmpDir;

protected $repositoryFactory;

public function __construct(VcsRepositoryFactory $repositoryFactory, string $tmpDir = null)
{
$this->repositoryFactory = $repositoryFactory;
$this->tmpDir = $tmpDir ?: sys_get_temp_dir();
}

/**
* @param SshCredentials|null $credentials
* @return \Composer\Config
*/
public function createConfig(SshCredentials $credentials = null)
{
$config = Factory::createConfig();

if (null !== $credentials) {
$credentialsFile = rtrim($this->tmpDir, '/') . '/packagist_priv_key_' . $credentials->getId();
if (!file_exists($credentialsFile)) {
file_put_contents($credentialsFile, $credentials->getKey());
chmod($credentialsFile, 0600);
}
putenv("GIT_SSH_COMMAND=ssh -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $credentialsFile");
ProcessExecutor::inheritEnv(['GIT_SSH_COMMAND']);

$config->merge(['config' => ['ssh-key-file' => $credentialsFile]]);
} else {
ProcessExecutor::inheritEnv([]);
putenv('GIT_SSH_COMMAND');
}

return $config;
}

/**
* @param string $url
* @param IOInterface|null $io
* @param Config|null $config
* @param SshCredentials|null $credentials
* @param array $repoConfig
*
* @return Repository\VcsRepository
*/
public function createRepository(string $url, IOInterface $io = null, Config $config = null, SshCredentials $credentials = null, array $repoConfig = [])
{
$io = $io ?: new NullIO();
if (null === $config) {
$config = $this->createConfig($credentials);
$io->loadConfiguration($config);
}

$repoConfig['url'] = $url;

return $this->repositoryFactory->create($repoConfig, $io, $config);
}
}
57 changes: 57 additions & 0 deletions src/Packagist/WebBundle/Composer/Repository/VcsRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Packagist\WebBundle\Composer\Repository;

use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Repository\Vcs\VcsDriver;
use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Repository\VcsRepository as ComposerVcsRepository;
use Composer\Repository\VersionCacheInterface;
use Packagist\WebBundle\Composer\VcsDriverFactory;

class VcsRepository extends ComposerVcsRepository
{
protected $drivers;

/** @var VcsDriverInterface|VcsDriver */
protected $driver = false;

/** @var VcsDriverFactory */
protected $driverFactory;

public function __construct(array $repoConfig, IOInterface $io, Config $config, VcsDriverFactory $driverFactory, EventDispatcher $dispatcher = null, VersionCacheInterface $versionCache = null)
{
parent::__construct($repoConfig, $io, $config, $dispatcher, [], $versionCache);
$this->driverFactory = $driverFactory;
}

/**
* @return VcsDriver|null
*/
public function getDriver()
{
if (false !== $this->driver) {
return $this->driver;
}

return $this->driver = $this->driverFactory->createDriver(
$this->repoConfig,
$this->io,
$this->config,
$this->type,
['url' => $this->url]
);
}

/**
* @return Config
*/
public function getConfig()
{
return $this->config;
}
}
86 changes: 86 additions & 0 deletions src/Packagist/WebBundle/Composer/Util/ProcessExecutor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Packagist\WebBundle\Composer\Util;

use Composer\Util\Platform;
use Composer\Util\ProcessExecutor as ComposerProcessExecutor;
use Symfony\Component\Process\Process;

class ProcessExecutor extends ComposerProcessExecutor
{
/**
* @var array
*/
protected static $inheritEnv = [];

/**
* {@inheritdoc}
*/
public function execute($command, &$output = null, $cwd = null)
{
if ($this->io && $this->io->isDebug()) {
$safeCommand = preg_replace_callback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', function ($m) {
if (preg_match('{^[a-f0-9]{12,}$}', $m['user'])) {
return '://***:***@';
}

return '://'.$m['user'].':***@';
}, $command);
$safeCommand = preg_replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand);
$this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand);
}

// make sure that null translate to the proper directory in case the dir is a symlink
// and we call a git command, because msysgit does not handle symlinks properly
if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) {
$cwd = realpath(getcwd());
}

$this->captureOutput = func_num_args() > 1;
$this->errorOutput = null;
$env = $this->getInheritedEnv();

// in v3, commands should be passed in as arrays of cmd + args
if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
$process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout());
} else {
$process = new Process($command, $cwd, $env, null, static::getTimeout());
}

$callback = is_callable($output) ? $output : array($this, 'outputHandler');
$process->run($callback);

if ($this->captureOutput && !is_callable($output)) {
$output = $process->getOutput();
}

$this->errorOutput = $process->getErrorOutput();

return $process->getExitCode();
}

/**
* Sets the environment variables for child process.
*
* @param array $variables
*/
public static function inheritEnv(?array $variables): void
{
static::$inheritEnv = $variables;
}

/**
* Sets the environment variables.
*/
protected function getInheritedEnv(): ?array
{
$env = [];
foreach (static::$inheritEnv as $name) {
if (getenv($name)) {
$env[$name] = getenv($name);
}
}

return $env ?: null;
}
}
102 changes: 102 additions & 0 deletions src/Packagist/WebBundle/Composer/VcsDriverFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Packagist\WebBundle\Composer;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Repository\Vcs\VcsDriver;
use Composer\Repository\Vcs\VcsDriverInterface;
use Packagist\WebBundle\Composer\Util\ProcessExecutor;

class VcsDriverFactory
{
/**
* @var array
*/
protected $drivers;

/**
* @param array $drivers
*/
public function __construct(array $drivers = [])
{
$this->drivers = $drivers ?: [
'github' => 'Composer\Repository\Vcs\GitHubDriver',
'gitlab' => 'Composer\Repository\Vcs\GitLabDriver',
'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
'git' => 'Composer\Repository\Vcs\GitDriver',
'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver',
'hg' => 'Composer\Repository\Vcs\HgDriver',
'perforce' => 'Composer\Repository\Vcs\PerforceDriver',
'fossil' => 'Composer\Repository\Vcs\FossilDriver',
// svn must be last because identifying a subversion server for sure is practically impossible
'svn' => 'Composer\Repository\Vcs\SvnDriver',
];
}

/**
* @param string $type
* @param string $class
*/
public function setDriverClass(string $type, string $class): void
{
$this->drivers[$type] = $class;
}

/**
* @param string $classOrType
* @param array $repoConfig
* @param IOInterface $io
* @param Config $config
* @param array $options
*
* @return VcsDriver|VcsDriverInterface
*/
public function createDriver(array $repoConfig, IOInterface $io, Config $config, string $classOrType = null, array $options = [])
{
$process = $this->createProcessExecutor($io);

$driver = null;
if ($classOrType && class_exists($classOrType)) {
$driver = new $classOrType($repoConfig, $io, $config, $process);
return $driver;
}

if (null === $driver && null !== $classOrType && isset($this->drivers[$classOrType])) {
$class = $this->drivers[$classOrType];
$driver = new $class($repoConfig, $io, $config);
return $driver;
}

if (null === $driver && isset($options['url'])) {
foreach ($this->drivers as $driverClass) {
if ($driverClass::supports($io, $config, $options['url'])) {
$driver = new $driverClass($repoConfig, $io, $config, $process);
break;
}
}
}

if (null === $driver && isset($options['url'])) {
foreach ($this->drivers as $driverClass) {
if ($driverClass::supports($io, $config, $options['url'], true)) {
$driver = new $driverClass($repoConfig, $io, $config, $process);
break;
}
}
}

if ($driver instanceof VcsDriverInterface) {
$driver->initialize();
}

return $driver;
}

protected function createProcessExecutor(IOInterface $io)
{
return new ProcessExecutor($io);
}
}
42 changes: 42 additions & 0 deletions src/Packagist/WebBundle/Composer/VcsRepositoryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Packagist\WebBundle\Composer;

use Composer\Config;
use Composer\IO\IOInterface;
use Packagist\WebBundle\Composer\Repository\VcsRepository;

class VcsRepositoryFactory
{
/**
* @var VcsDriverFactory
*/
protected $driverFactory;

/**
* @param VcsDriverFactory $driverFactory
*/
public function __construct(VcsDriverFactory $driverFactory)
{
$this->driverFactory = $driverFactory;
}

/**
* @param array $repoConfig
* @param IOInterface $io
* @param Config $config
*
* @return VcsRepository
*/
public function create(array $repoConfig, IOInterface $io, Config $config)
{
return new VcsRepository(
$repoConfig,
$io,
$config,
$this->driverFactory
);
}
}
Loading

0 comments on commit ace7300

Please sign in to comment.