diff --git a/qit b/qit index 109d1b01..7b4698d2 100755 Binary files a/qit and b/qit differ diff --git a/src/src/Auth.php b/src/src/Auth.php index c62f1a74..6be70bd7 100644 --- a/src/src/Auth.php +++ b/src/src/Auth.php @@ -13,12 +13,17 @@ public function __construct( Environment $environment ) { /** * @return string|null base64 encoded string of user:application_password, or null if not defined. */ - public function get_app_pass() { - $user = $this->environment->get_cache()->get( 'user' ); - $application_password = $this->environment->get_cache()->get( 'application_password' ); + public function get_partner_auth() { + // Migrate "application_password" to "qit_token" if it exists. + if ( empty( $this->environment->get_cache()->get( 'qit_token' ) ) && ! empty( $this->environment->get_cache()->get( 'application_password' ) ) ) { + $this->environment->get_cache()->set( 'qit_token', $this->environment->get_cache()->get( 'application_password' ), - 1 ); + } + + $user = $this->environment->get_cache()->get( 'user' ); + $qit_token = $this->environment->get_cache()->get( 'qit_token' ); - if ( ! empty( $user ) && ! empty( $application_password ) ) { - return base64_encode( sprintf( '%s:%s', $user, $application_password ) ); + if ( ! empty( $user ) && ! empty( $qit_token ) ) { + return base64_encode( sprintf( '%s:%s', $user, $qit_token ) ); } return null; @@ -33,13 +38,13 @@ public function get_manager_secret() { /** * @param string $user - * @param string $app_pass + * @param string $qit_token * * @return void */ - public function set_auth_app_pass( $user, $app_pass ): void { + public function set_partner_auth( $user, $qit_token ): void { $this->environment->get_cache()->set( 'user', $user, - 1 ); - $this->environment->get_cache()->set( 'application_password', $app_pass, - 1 ); + $this->environment->get_cache()->set( 'qit_token', $qit_token, - 1 ); } /** diff --git a/src/src/Commands/OnboardingCommand.php b/src/src/Commands/OnboardingCommand.php index cda3f5d8..26a6ff18 100644 --- a/src/src/Commands/OnboardingCommand.php +++ b/src/src/Commands/OnboardingCommand.php @@ -10,7 +10,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Console\Terminal; use function QIT_CLI\get_manager_url; class OnboardingCommand extends Command { @@ -23,37 +22,7 @@ protected function configure() { protected function execute( InputInterface $input, OutputInterface $output ): int { $io = new SymfonyStyle( $input, $output ); - $io->title( 'Woo Quality Insights Toolkit CLI' ); - - $io->section( <<
writeln( 'Examples:' ); - - $io->writeln( "\nRunning a Security Test:" ); - $io->writeln( './qit run:security my-extension-slug' ); - - $io->writeln( "\nRunning a Security Test against a development build:" ); - $io->writeln( './qit run:security my-extension-slug --zip=my-extension-slug.zip' ); - - $io->writeln( "\nRunning a WooCommerce Core E2E test with configurable options against a dev build:" ); - $io->writeln(<<writeln( sprintf( '%s', str_repeat( '-', ( new Terminal() )->getWidth() ) ) ); + $io->title( 'Quality Insights Toolkit (QIT)' ); $output->write( "\nConnecting to QIT servers... " ); @@ -70,26 +39,25 @@ protected function execute( InputInterface $input, OutputInterface $output ): in $question_helper = $this->getHelper( 'question' ); if ( $is_proxied ) { - $io->section( 'Onboarding as Automattician (Connected to Proxy)' ); + $io->section( 'Authenticate as Automattician (Connected to Proxy)' ); $io->writeln( $proxied_instructions . "\n" ); $user = new Question( 'Please enter the user: ' ); $user = $question_helper->ask( $input, $output, $user ); - $app_pass = new Question( 'Please enter the application password: ' ); + $app_pass = new Question( 'Please enter the QIT Token: ' ); $app_pass->setHidden( true ); $app_pass = $question_helper->ask( $input, $output, $app_pass ); $command = $this->getApplication()->find( AddPartner::getDefaultName() ); return $command->run( new ArrayInput( [ - '--user' => $user, - '--application_password' => $app_pass, + '--user' => $user, + '--qit_token' => $app_pass, ] ), $output ); } else { - $io->section( 'Onboarding as Partner of the Woo Marketplace' ); - $io->info( 'If you are an Automattic employee, please connect to the proxy and run the CLI again.' ); + $io->section( 'Authenticate as Woo.com Partner Developer' ); $command = $this->getApplication()->find( AddPartner::getDefaultName() ); diff --git a/src/src/Commands/Partner/AddPartner.php b/src/src/Commands/Partner/AddPartner.php index 9b141555..0c60eb44 100644 --- a/src/src/Commands/Partner/AddPartner.php +++ b/src/src/Commands/Partner/AddPartner.php @@ -10,6 +10,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; use function QIT_CLI\get_manager_url; use function QIT_CLI\get_wccom_url; use function QIT_CLI\validate_authentication; @@ -36,9 +37,10 @@ public function __construct( Environment $environment, Auth $auth, WooExtensions protected function configure() { $this ->setDescription( 'Configure a new WCCOM Marketplace Partner that the QIT CLI can connect to.' ) - ->setHelp( sprintf( 'Configure the QIT CLI to be able to interact with %s on behalf of a given partner.', get_wccom_url() ) ) - ->addOption( 'user', 'u', InputOption::VALUE_OPTIONAL, '(Optional) WCCOM Marketplace user with "edit" permission to the extensions that you want to test.' ) - ->addOption( 'application_password', 'p', InputOption::VALUE_OPTIONAL, '(Optional) Partner application password.' ); + ->setHelp( sprintf( "Configure the QIT CLI to be able to interact with %s on behalf of a given partner.\nAuthenticating documentation: https://woocommerce.github.io/qit-documentation/#/authenticating", get_wccom_url() ) ) + ->addOption( 'user', 'u', InputOption::VALUE_OPTIONAL, '(Optional) Woo.com Partner Developer username.' ) + ->addOption( 'qit_token', 't', InputOption::VALUE_OPTIONAL, '(Optional) Woo.com Partner Developer QIT Token.' ) + ->addOption( 'application_password', 'p', InputOption::VALUE_OPTIONAL, '(DEPRECATED) This has been renamed to "QIT Token" and will be removed. A regular application password will not work.' ); } protected function execute( InputInterface $input, OutputInterface $output ): int { @@ -50,52 +52,59 @@ protected function execute( InputInterface $input, OutputInterface $output ): in $user = $this->getHelper( 'question' )->ask( $input, $output, $question ); } - // Application Password. + $qit_token = null; + + // (Deprecated) Application password. if ( ! empty( $input->getOption( 'application_password' ) ) ) { - $application_password = $input->getOption( 'application_password' ); - } else { + $output->writeln( '"Application Password" has been renamed to "QIT Token" and will be removed in the future.' ); + $qit_token = $input->getOption( 'application_password' ); + } + + // Or QIT Token. + if ( ! empty( $input->getOption( 'qit_token' ) ) ) { + $qit_token = $input->getOption( 'qit_token' ); + } + + // Ask for QIT Token. + if ( is_null( $qit_token ) ) { $authorize_url = $this->get_authorize_url( $output ); $wccom_url = get_wccom_url(); $output->writeln( <<If you are redirected back to "my-account" on step 3, it's because you are not logged in with a Partner account. -Contact someone in your organization that has access to the Partner account in WCCOM to generate the Application Password for you. +1. Login as "$user" in $wccom_url +2. Go to $authorize_url +3. Authorize the connection, copy the generated QIT Token and paste it here. Note: The input is protected, so you won't be able to see it on your terminal. TEXT ); - $question = new Question( "Please paste the Application Password here and press 'Enter' " ); + $question = new Question( "Please paste the QIT Token here and press 'Enter' " ); $question->setHidden( true ); $question->setHiddenFallback( false ); $question->setMaxAttempts( 1 ); - $question->setValidator( function ( $application_password ) { - if ( empty( $application_password ) ) { - throw new \RuntimeException( 'Invalid Application Password.' ); + $question->setValidator( function ( $qit_token ) { + if ( empty( $qit_token ) ) { + throw new \RuntimeException( 'Invalid QIT Token. Questions? https://woocommerce.github.io/qit-documentation/#/authenticating' ); } - if ( ! preg_match( '#^[a-z0-9 ]+$#i', $application_password ) ) { - throw new \RuntimeException( 'An application password should consist of alpha-numeric characters and spaces.' ); + if ( ! preg_match( '#^[a-z0-9 ]+$#i', $qit_token ) ) { + throw new \RuntimeException( 'A QIT Token should consist of alpha-numeric characters and spaces. Questions? https://woocommerce.github.io/qit-documentation/#/authenticating' ); } - return $application_password; + return $qit_token; } ); - $question->setNormalizer( function ( $application_password ) { - return str_replace( ' ', '', $application_password ); + $question->setNormalizer( function ( $qit_token ) { + return str_replace( ' ', '', $qit_token ); } ); - $application_password = $this->getHelper( 'question' )->ask( $input, $output, $question ); + $qit_token = $this->getHelper( 'question' )->ask( $input, $output, $question ); } $manager_url = get_manager_url(); @@ -103,15 +112,15 @@ protected function execute( InputInterface $input, OutputInterface $output ): in $user = strtolower( $user ); if ( ! filter_var( $user, FILTER_VALIDATE_EMAIL ) && ! preg_match( '#^[a-z0-9_-]{1,70}$#i', $user ) ) { - throw new \InvalidArgumentException( 'The username must be either a valid e-mail, or contain only letters, numbers, underscores or dashes.' ); + throw new \InvalidArgumentException( 'The username must be either a valid e-mail, or contain only letters, numbers, underscores or dashes. Questions? https://woocommerce.github.io/qit-documentation/#/authenticating' ); } // Remove any non-alphanumeric characters from the username. $user_environment = preg_replace( '#[^a-z0-9_-]#i', '', $user ); // Validate credentials. - $output->writeln( sprintf( 'Validating your application password with %s...', get_wccom_url() ) ); - validate_authentication( $user, $application_password ); + $output->writeln( sprintf( 'Validating your QIT Token with %s...', get_wccom_url() ) ); + validate_authentication( $user, $qit_token ); $output->writeln( 'Validated successfully.' ); $easter_egg = <<environment->create_partner( $user_environment ); - $this->auth->set_auth_app_pass( $user, $application_password ); + $this->auth->set_partner_auth( $user, $qit_token ); $this->environment->get_cache()->set( 'manager_url', $manager_url, - 1 ); $this->woo_extensions_list->fetch_woo_extensions_available(); - $output->writeln( sprintf( "Cache file written to: %s. Keep this file safe, as it contains your Application Password.\n", $this->environment->get_cache()->get_cache_file_path() ) ); + $output->writeln( sprintf( "Cache file written to: %s. Keep this file safe, as it contains your QIT Token.\n", $this->environment->get_cache()->get_cache_file_path() ) ); $output->writeln( "Treat this file as you would treat your SSH keys. For more tips on hardening security, check the README of the QIT CLI.\n" ); $output->writeln( 'Initialization complete! You can now start using the QIT CLI!' ); + $io = new SymfonyStyle( $input, $output ); + + $io->section( <<
writeln( 'Examples:' ); + + $io->writeln( "\nRunning a Security Test:" ); + $io->writeln( './qit run:security my-extension-slug' ); + + $io->writeln( "\nRunning a Security Test against a development build:" ); + $io->writeln( './qit run:security my-extension-slug --zip=plugin.zip' ); + + $io->writeln( "\nRunning a WooCommerce Core E2E test with configurable options using a development build:" ); + $io->writeln(<<get_app_pass() ) ) { - $this->post_body['partner_app_pass'] = App::make( Auth::class )->get_app_pass(); + } elseif ( ! is_null( App::make( Auth::class )->get_partner_auth() ) ) { + $this->post_body['partner_app_pass'] = App::make( Auth::class )->get_partner_auth(); } } diff --git a/src/src/helpers.php b/src/src/helpers.php index cb6a3912..9c9a576a 100644 --- a/src/src/helpers.php +++ b/src/src/helpers.php @@ -6,7 +6,7 @@ function is_windows(): bool { return defined( 'PHP_WINDOWS_VERSION_BUILD' ); } -function validate_authentication( string $username, string $application_password ): void { +function validate_authentication( string $username, string $qit_token ): void { $is_ci = getenv( 'CI' ); try { @@ -14,11 +14,11 @@ function validate_authentication( string $username, string $application_password ->with_method( 'POST' ) ->with_retry( $is_ci ? 8 : 2 ) // Retry many times due to parallel test runs in CI which might cause 429. ->with_post_body( [ - 'app_pass' => base64_encode( sprintf( '%s:%s', $username, $application_password ) ), + 'app_pass' => base64_encode( sprintf( '%s:%s', $username, $qit_token ) ), ] ) ->request(); } catch ( \Exception $e ) { - throw new \Exception( sprintf( 'Could not authenticate to %s using the provided username and application password.', get_wccom_url() ) ); + throw new \Exception( sprintf( 'Could not authenticate to %s using the provided username and QIT Token. Questions? https://woocommerce.github.io/qit-documentation/#/authenticating', get_wccom_url() ) ); } } diff --git a/src/tests/__snapshots__/PartnerManagementTest__test_add_partner__1.json b/src/tests/__snapshots__/PartnerManagementTest__test_add_partner__1.json index 5731fb92..fa697b1d 100644 --- a/src/tests/__snapshots__/PartnerManagementTest__test_add_partner__1.json +++ b/src/tests/__snapshots__/PartnerManagementTest__test_add_partner__1.json @@ -3,7 +3,7 @@ "expire": -1, "value": "foo_user" }, - "application_password": { + "qit_token": { "expire": -1, "value": "foo_pass" }, diff --git a/src/tests/__snapshots__/PartnerManagementTest__test_add_partner_email__1.json b/src/tests/__snapshots__/PartnerManagementTest__test_add_partner_email__1.json index 3d7ba42c..af13c1e7 100644 --- a/src/tests/__snapshots__/PartnerManagementTest__test_add_partner_email__1.json +++ b/src/tests/__snapshots__/PartnerManagementTest__test_add_partner_email__1.json @@ -3,7 +3,7 @@ "expire": -1, "value": "foo_user@bar.com" }, - "application_password": { + "qit_token": { "expire": -1, "value": "foo_pass" }, diff --git a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__1.json b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__1.json index 89148f52..f9dd758e 100644 --- a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__1.json +++ b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__1.json @@ -3,7 +3,7 @@ "expire": -1, "value": "bar_user" }, - "application_password": { + "qit_token": { "expire": -1, "value": "bar_pass" }, diff --git a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__2.json b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__2.json index 5731fb92..fa697b1d 100644 --- a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__2.json +++ b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner__2.json @@ -3,7 +3,7 @@ "expire": -1, "value": "foo_user" }, - "application_password": { + "qit_token": { "expire": -1, "value": "foo_pass" }, diff --git a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__1.json b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__1.json index 2819fa37..bb3841ba 100644 --- a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__1.json +++ b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__1.json @@ -3,7 +3,7 @@ "expire": -1, "value": "staging_user" }, - "application_password": { + "qit_token": { "expire": -1, "value": "staging_pass" }, diff --git a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__2.json b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__2.json index 0edd82b1..9af92431 100644 --- a/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__2.json +++ b/src/tests/__snapshots__/PartnerManagementTest__test_switch_partner_on_different_environments__2.json @@ -3,7 +3,7 @@ "expire": -1, "value": "local_user" }, - "application_password": { + "qit_token": { "expire": -1, "value": "local_pass" },