protected function processParams($params, $actualParams) { if (!$this->io->isInteractive()) { return $actualParams; } $this->io->write('<comment>Some parameters are missing. Please provide them.</comment>'); foreach ($params as $key => $value) { $result = $this->io->ask(sprintf("<question>%s</question> (<comment>%s</comment>): ", $this->messages[$key], $value)); if (!empty($result)) { $actualParams[$key] = $result; } } return $actualParams; }
/** * Print choices * * @param string $label * @param array $choices * @return void */ public function printChoices($label, array $choices) { if ($this->io->isInteractive() && !empty($choices)) { $this->io->write(static::PADDING . $label); foreach ($choices as $choice => $description) { $this->io->write(static::PADDING . " * <info>{$choice}</info>: {$description}"); } } }
/** * Get actual params and display Q&A. * * @param array $expectedParams * @param array $actualValues * @param string $realFile * @return array $values * * @since 0.0.3 */ private function getParams(array $expectedParams, array $actualValues, $realFile) { // Simply use the expectedParams value as default for the missing params. if (!$this->io->isInteractive()) { $this->io->write(sprintf("<comment>Interactions are not permitted.\nPlease, edit your \"%s\" file manually to define properly your parameters.</comment>\n", $realFile)); return array_replace($expectedParams, $actualValues); } // Get forgotten keys $keys = $this->keyMatch($expectedParams, $actualValues); // Iterate on expectedParams and display Q&A return $this->treatParams($keys); }
/** * Repositories requests credentials, let's put them in. * * @throws \RuntimeException * @return \Composer\Util\Svn */ protected function doAuthDance() { // cannot ask for credentials in non interactive mode if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode'); } $this->io->writeError("The Subversion server ({$this->url}) requested credentials:"); $this->hasAuth = true; $this->credentials['username'] = $this->io->ask("Username: "******"Password: "******"Should Subversion cache these credentials? (yes/no) ", true); return $this; }
public function promptAuth(HttpGetResponse $res, CConfig $config, IO\IOInterface $io) { $httpCode = $res->info['http_code']; $message = "\nCould not fetch {$this->getURL()}, please create a GitHub OAuth token "; if (404 === $httpCode) { $message .= 'to access private repos'; } else { $message .= 'to go over the API rate limit'; } $github = new Util\GitHub($io, $config, null); if ($github->authorizeOAuth($this->origin)) { return true; } if ($io->isInteractive() && $github->authorizeOAuthInteractively($this->origin, $message)) { return true; } throw new Downloader\TransportException("Could not authenticate against {$this->origin}", 401); }
/** * Asks the user for confirmation on some action to be taken. * * @param IOInterface $io IO interface to write to console. * @param string $question * @param boolean $default Default answer * @return boolean */ protected static function confirmAction(IOInterface $io, $question, $default = true) { if (!$io->isInteractive()) { return $default; } $validator = function ($arg) { if (in_array($arg, ['Y', 'y', 'N', 'n'])) { return $arg; } throw new Exception('This is not a valid answer. Please choose Y or n.'); }; $defaultAnswer = $default ? 'Y' : 'n'; $msg = '<question>' . $question . '</question> ' . '<info>(Default to ' . $defaultAnswer . ')</info> ' . '[<comment>Y,n</comment>]: '; $answer = $io->askAndValidate($msg, $validator, 10, $defaultAnswer); if (in_array($answer, ['Y', 'y'])) { return true; } return $default; }
/** * Prompt for missing values. If IO isn't interactive, use all defaults. * * @param array $missing List * @param boolean $isStarted Shared state if the header was already outputed. * @return array */ protected function processIO(array $missing, &$isStarted = false, $prefix = '') { // Simply use the expectedParams value as default for the missing params. if (!$this->io->isInteractive()) { return $missing; } foreach ($missing as $key => $default) { $fqk = $prefix . $key; if (is_array($default)) { $missing[$key] = $this->processIO($default, $isStarted, $fqk . '.'); continue; } if (!$isStarted) { $isStarted = true; $this->io->write(sprintf('<comment>Some %s parameters are missing. Please provide them.</comment>', $this->name)); } $missing[$key] = $this->askIO($fqk, $default); } return $missing; }
private static function getParams(IOInterface $io, array $expectedParams, array $actualParams) { // Simply use the expectedParams value as default for the missing params. if (!$io->isInteractive()) { return array_replace($expectedParams, $actualParams); } $isStarted = false; foreach ($expectedParams as $key => $message) { if (array_key_exists($key, $actualParams)) { continue; } if (!$isStarted) { $isStarted = true; $io->write('<comment>Some parameters are missing. Please provide them.</comment>'); } $default = Inline::dump($message); $value = $io->ask(sprintf('<question>%s</question> (<comment>%s</comment>):', $key, $default), $default); $actualParams[$key] = Inline::parse($value); } return $actualParams; }
/** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param bool $verbose Output all output to the user * * @return string * * @throws \RuntimeException */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return; } if ('Redirecting to URL ' === substr($buffer, 0, 19)) { return; } $output .= $buffer; if ($verbose) { $io->write($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } if (empty($output)) { $output = $this->process->getErrorOutput(); } // the error is not auth-related if (false === stripos($output, 'Could not authenticate to server:') && false === stripos($output, 'authorization failed') && false === stripos($output, 'svn: E170001:') && false === stripos($output, 'svn: E215004:')) { throw new \RuntimeException($output); } // no auth supported for non interactive calls if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode (' . $output . ')'); } // try to authenticate if maximum quantity of tries not reached if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES || !$this->hasAuth()) { $this->doAuthDance(); // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $output . ')'); }
/** * Execute an SVN command and try to fix up the process with credentials * if necessary. * * @param string $command SVN command to run * @param string $url SVN url * @param string $cwd Working directory * @param string $path Target for a checkout * @param Boolean $verbose Output all output to the user * * @return string * * @throws \RuntimeException */ public function execute($command, $url, $cwd = null, $path = null, $verbose = false) { $svnCommand = $this->getCommand($command, $url, $path); $output = null; $io = $this->io; $handler = function ($type, $buffer) use(&$output, $io, $verbose) { if ($type !== 'out') { return; } $output .= $buffer; if ($verbose) { $io->write($buffer, false); } }; $status = $this->process->execute($svnCommand, $handler, $cwd); if (0 === $status) { return $output; } if (empty($output)) { $output = $this->process->getErrorOutput(); } // the error is not auth-related if (false === stripos($output, 'authorization failed:')) { throw new \RuntimeException($output); } // no auth supported for non interactive calls if (!$this->io->isInteractive()) { throw new \RuntimeException('can not ask for authentication in non interactive mode (' . $output . ')'); } // TODO keep a count of user auth attempts and ask 5 times before // failing hard (currently it fails hard directly if the URL has credentials) // try to authenticate if (!$this->hasAuth()) { $this->doAuthDance(); // restart the process return $this->execute($command, $url, $cwd, $path, $verbose); } throw new \RuntimeException('wrong credentials provided (' . $output . ')'); }
/** * @brief Ask for a path to a file * @param IOInterface $io * @param string $filename parameters.yml or parameters_shared.yml * @param string $pathname file to look for before asking in IO * @throws \Exception * @internal param Event $event */ protected static function getFromIO(IOInterface $io, $filename, $pathname = "") { if (!file_exists($pathname . "/" . $filename)) { if ($io->isInteractive()) { $try = 0; while (!file_exists($pathname . '/' . $filename)) { if ($try >= 3) { throw new \InvalidArgumentException("3 attempts exhausted, {$filename} not found!"); } if ($try > 0) { $io->write("<error>File <options=bold;fg=yellow>{$pathname}/{$filename}</options=bold;fg=yellow> does not exist!</error>"); } $try++; $pathname = $io->ask("<comment>Path to folder containing '{$filename}':</comment> ", $pathname); } } else { throw new \Exception("Failed to retrieve {$filename} from IO: Input is not interactive"); } } $dest = self::PARAMETERS_DIR . $filename; copy($pathname . '/' . $filename, $dest); $io->write("<info>File {$pathname}/{$filename} copied to {$dest}</info>"); }
public function runCommand($commandCallable, $url, $cwd, $initialClone = false) { if (preg_match('{^(http|git):}i', $url) && $this->config->get('secure-http')) { throw new TransportException("Your configuration does not allow connection to {$url}. See https://getcomposer.org/doc/06-config.md#secure-http for details."); } if ($initialClone) { $origCwd = $cwd; $cwd = null; } if (preg_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) { throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } if (!$initialClone) { // capture username/password from URL if there is one $this->process->execute('git remote -v', $output, $cwd); if (preg_match('{^(?:composer|origin)\\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) { $this->io->setAuthentication($match[3], urldecode($match[1]), urldecode($match[2])); } } $protocols = $this->config->get('github-protocols'); if (!is_array($protocols)) { throw new \RuntimeException('Config value "github-protocols" must be an array, got ' . gettype($protocols)); } // public github, autoswitch protocols if (preg_match('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) { $messages = array(); foreach ($protocols as $protocol) { if ('ssh' === $protocol) { $protoUrl = "git@" . $match[1] . ":" . $match[2]; } else { $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2]; } if (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $ignoredOutput, $cwd)) { return; } $messages[] = '- ' . $protoUrl . "\n" . preg_replace('#^#m', ' ', $this->process->getErrorOutput()); if ($initialClone) { $this->filesystem->removeDirectory($origCwd); } } // failed to checkout, first check git accessibility $this->throwException('Failed to clone ' . self::sanitizeUrl($url) . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url); } // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https $bypassSshForGitHub = preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\\.git$}i', $url) && !in_array('ssh', $protocols, true); $command = call_user_func($commandCallable, $url); $auth = null; if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { // private github repository without git access, try https with auth if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\\.git$}i', $url, $match)) { if (!$this->io->hasAuthentication($match[1])) { $gitHubUtil = new GitHub($this->io, $this->config, $this->process); $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) { $gitHubUtil->authorizeOAuthInteractively($match[1], $message); } } if ($this->io->hasAuthentication($match[1])) { $auth = $this->io->getAuthentication($match[1]); $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git'; $command = call_user_func($commandCallable, $authUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { return; } } } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate if (strpos($match[2], '@')) { list($authParts, $match[2]) = explode('@', $match[2], 2); } $storeAuth = false; if ($this->io->hasAuthentication($match[2])) { $auth = $this->io->getAuthentication($match[2]); } elseif ($this->io->isInteractive()) { $defaultUsername = null; if (isset($authParts) && $authParts) { if (false !== strpos($authParts, ':')) { list($defaultUsername, ) = explode(':', $authParts, 2); } else { $defaultUsername = $authParts; } } $this->io->writeError(' Authentication required (<info>' . parse_url($url, PHP_URL_HOST) . '</info>):'); $auth = array('username' => $this->io->ask(' Username: '******'password' => $this->io->askAndHideAnswer(' Password: '******'store-auths'); } if ($auth) { $authUrl = $match[1] . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . $match[3]; $command = call_user_func($commandCallable, $authUrl); if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { $this->io->setAuthentication($match[2], $auth['username'], $auth['password']); $authHelper = new AuthHelper($this->io, $this->config); $authHelper->storeAuth($match[2], $storeAuth); return; } } } if ($initialClone) { $this->filesystem->removeDirectory($origCwd); } $this->throwException('Failed to execute ' . self::sanitizeUrl($command) . "\n\n" . $this->process->getErrorOutput(), $url); } }
protected function fetchKeys(IOInterface $io, Config $config) { if (!$io->isInteractive()) { throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively'); } $io->write('Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys'); $validator = function ($value) { if (!preg_match('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) { throw new \UnexpectedValueException('Invalid input'); } return trim($value) . "\n"; }; $devKey = ''; while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) { $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator); while ($line = $io->ask('')) { $devKey .= trim($line) . "\n"; if (trim($line) === '-----END PUBLIC KEY-----') { break; } } } file_put_contents($keyPath = $config->get('home') . '/keys.dev.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $tagsKey = ''; while (!preg_match('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) { $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator); while ($line = $io->ask('')) { $tagsKey .= trim($line) . "\n"; if (trim($line) === '-----END PUBLIC KEY-----') { break; } } } file_put_contents($keyPath = $config->get('home') . '/keys.tags.pub', $match[0]); $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath)); $io->write('Public keys stored in ' . $config->get('home')); }
public function promptAuth(HttpGetResponse $res, CConfig $config, IO\IOInterface $io) { $httpCode = $res->info['http_code']; // 404s are only handled for github if (404 === $httpCode) { return false; } // fail if the console is not interactive if (!$io->isInteractive()) { switch ($httpCode) { case 401: $message = "The '{$this->getURL()}' URL required authentication.\nYou must be using the interactive console to authenticate"; break; case 403: $message = "The '{$this->getURL()}' URL could not be accessed."; break; } throw new Downloader\TransportException($message, $httpCode); } // fail if we already have auth if ($io->hasAuthentication($this->origin)) { throw new Downloader\TransportException("Invalid credentials for '{$this->getURL()}', aborting.", $httpCode); } $io->overwrite(" Authentication required (<info>{$this->host}</info>):"); $username = $io->ask(' Username: '******' Password: '); $io->setAuthentication($this->origin, $username, $password); return true; }
/** * @param IOInterface $io * @param $parameters * @return bool */ private static function askConfirmation(IOInterface $io, $parameters) { if (!$io->isInteractive()) { return true; } $confirmation = $io->askConfirmation(sprintf('Do you want to create MySQL database \'%s\' and install Magento on it [Y,n]?', $parameters['db_name']), true); return $confirmation; }