From 7eb7529be9c43e9a1ff69bf1dd974ed3b123241e Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Thu, 17 Aug 2017 18:29:49 -0400 Subject: Bug 1388148 - Test password quality checking code --- .circleci/checksetup_answers.txt | 4 +- .circleci/config.yml | 37 +++++- Bugzilla/Test/Util.pm | 32 +++++ scripts/entrypoint.pl | 22 +++- scripts/generate_bmo_data.pl | 4 +- t/bmo/passwords.t | 260 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 351 insertions(+), 8 deletions(-) create mode 100644 Bugzilla/Test/Util.pm create mode 100644 t/bmo/passwords.t diff --git a/.circleci/checksetup_answers.txt b/.circleci/checksetup_answers.txt index 80a1d40d2..bcdefa38e 100644 --- a/.circleci/checksetup_answers.txt +++ b/.circleci/checksetup_answers.txt @@ -1,11 +1,13 @@ $answer{'ADMIN_EMAIL'} = 'admin@mozilla.bugs'; $answer{'ADMIN_OK'} = 'Y'; -$answer{'ADMIN_PASSWORD'} = 'password'; +$answer{'ADMIN_PASSWORD'} = 'passWord1234!'; $answer{'ADMIN_REALNAME'} = 'QA Admin'; $answer{'NO_PAUSE'} = 1; $answer{'bugzilla_version'} = '1'; $answer{'create_htaccess'} = '1'; $answer{'cvsbin'} = '/usr/bin/cvs'; +$answer{'password_complexity'} = 'bmo'; $answer{'diffpath'} = '/usr/bin'; $answer{'interdiffbin'} = '/usr/bin/interdiff'; $answer{'urlbase'} = 'http://<>:8000/'; +$answer{'mail_delivery_method'} = 'Test'; diff --git a/.circleci/config.yml b/.circleci/config.yml index 619f1cb11..cd2ec0e10 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -120,13 +120,48 @@ jobs: - store_artifacts: path: /app/artifacts + test_bmo: + parallelism: 4 + working_directory: /app + docker: + - <<: *bmo_slim_image + environment: + <<: *bmo_env + BZ_QA_ANSWERS_FILE: /app/.circleci/checksetup_answers.txt + TWD_HOST: localhost + TWD_PORT: 4444 + TWD_BROWSER: firefox + - <<: *mysql_image + environment: *mysql_env + - image: memcached:latest + - image: selenium/standalone-firefox:2.53.1 + steps: + - checkout + - run: | + mv /opt/bmo/local /app/local + perl checksetup.pl --no-database --default-localconfig + perl -MSys::Hostname -i -pE 's/<>/hostname()/ges' $BZ_QA_ANSWERS_FILE + rm -f /app/localconfig + /app/scripts/entrypoint.pl load_test_data + mkdir artifacts + - run: | + BZ_BASE_URL="http://$(hostname):$PORT" + export BZ_BASE_URL + rm -f /app/localconfig + /app/scripts/entrypoint.pl test_bmo -q -f t/bmo/*.t + + + workflows: version: 2 tests: jobs: + - test_bmo - test_sanity - test_webservices - - test_selenium + - test_selenium: + requires: + - test_bmo - build: requires: - test_sanity diff --git a/Bugzilla/Test/Util.pm b/Bugzilla/Test/Util.pm new file mode 100644 index 000000000..4c9981e52 --- /dev/null +++ b/Bugzilla/Test/Util.pm @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Test::Util; + +use 5.10.1; +use strict; +use warnings; + +use base qw(Exporter); +our @EXPORT = qw(create_user); + +use Bugzilla::User; + +sub create_user { + my ($login, $password, %extra) = @_; + require Bugzilla; + return Bugzilla::User->create({ + login_name => $login, + cryptpassword => $password, + disabledtext => "", + disable_mail => 0, + extern_id => 0, + %extra, + }); +} + +1; diff --git a/scripts/entrypoint.pl b/scripts/entrypoint.pl index 2d1ef8fe9..c39551131 100755 --- a/scripts/entrypoint.pl +++ b/scripts/entrypoint.pl @@ -3,16 +3,18 @@ use 5.10.1; use strict; use warnings; use lib qw(/app /app/local/lib/perl5); + use Bugzilla::Install::Localconfig (); use Bugzilla::Install::Util qw(install_string); +use Bugzilla::Test::Util qw(create_user); use DBI; use Data::Dumper; use English qw($EUID); use File::Copy::Recursive qw(dircopy); use Getopt::Long qw(:config gnu_getopt); use LWP::Simple qw(get); -use User::pwent; use POSIX qw(WEXITSTATUS setsid); +use User::pwent; use IO::Async::Loop; use IO::Async::Process; @@ -53,7 +55,7 @@ sub cmd_load_test_data { run( 'perl', 'generate_test_data.pl' ); } else { - run( 'perl', 'scripts/generate_bmo_data.pl' ); + run( 'perl', 'scripts/generate_bmo_data.pl', '--param' => 'use_mailer_queue=0' ); } } @@ -76,6 +78,8 @@ sub cmd_test_webservices { my $conf = require $ENV{BZ_QA_CONF_FILE}; check_data_dir(); + wait_for_db(); + my @httpd_cmd = ( '/usr/sbin/httpd', '-DFOREGROUND', '-f', '/app/httpd/httpd.conf' ); if ($ENV{BZ_QA_LEGACY_MODE}) { copy_qa_extension(); @@ -98,6 +102,7 @@ sub cmd_test_selenium { my $conf = require $ENV{BZ_QA_CONF_FILE}; check_data_dir(); + wait_for_db(); my @httpd_cmd = ( '/usr/sbin/httpd', '-DFOREGROUND', '-f', '/app/httpd/httpd.conf' ); if ($ENV{BZ_QA_LEGACY_MODE}) { copy_qa_extension(); @@ -121,6 +126,16 @@ sub cmd_prove { run( "prove", "-I/app", "-I/app/local/lib/perl5", @_ ); } sub cmd_version { run( 'cat', '/app/version.json' ); } sub cmd_test_bmo { + check_data_dir(); + wait_for_db(); + + $ENV{BZ_TEST_NEWBIE} = 'newbie@mozilla.example'; + $ENV{BZ_TEST_NEWBIE_PASS} = 'captain.space.bagel.ROBOT!'; + create_user($ENV{BZ_TEST_NEWBIE}, $ENV{BZ_TEST_NEWBIE_PASS}, realname => "Newbie User"); + + $ENV{BZ_TEST_NEWBIE2} = 'newbie2@mozilla.example'; + $ENV{BZ_TEST_NEWBIE2_PASS} = 'captain.space.pants.time.lord'; + prove_with_httpd( httpd_url => $ENV{BZ_BASE_URL}, httpd_cmd => [ '/usr/sbin/httpd', '-f', '/app/httpd/httpd.conf', '-DFOREGROUND' ], @@ -131,9 +146,6 @@ sub cmd_test_bmo { sub prove_with_httpd { my (%param) = @_; - check_data_dir(); - wait_for_db(); - unless (-d "/app/logs") { mkdir("/app/logs") or die "unable to mkdir(/app/logs): $!\n"; } diff --git a/scripts/generate_bmo_data.pl b/scripts/generate_bmo_data.pl index 788227443..b2df7ddba 100755 --- a/scripts/generate_bmo_data.pl +++ b/scripts/generate_bmo_data.pl @@ -63,7 +63,8 @@ my %user_prefs = ( zoom_textareas => 'off', ); -GetOptions('user-pref=s%' => \%user_prefs); +my %opt_param; +GetOptions('user-pref=s%' => \%user_prefs, 'param=s' => \%opt_param); my $admin_email = shift || 'admin@mozilla.bugs'; Bugzilla->set_user(Bugzilla::User->check({ name => $admin_email })); @@ -496,6 +497,7 @@ my %set_params = ( use_mailer_queue => 1, user_info_class => 'GitHubAuth,CGI', user_verify_class => 'GitHubAuth,DB', + %opt_param, ); my $params_modified; diff --git a/t/bmo/passwords.t b/t/bmo/passwords.t new file mode 100644 index 000000000..d10eddff7 --- /dev/null +++ b/t/bmo/passwords.t @@ -0,0 +1,260 @@ +#!/usr/bin/env perl +use 5.10.1; +use strict; +use warnings; +use autodie; +use constant DRIVER => 'Test::Selenium::Remote::Driver'; + +use Test::More 1.302; +#use constant DRIVER => 'Test::Selenium::Chrome'; +BEGIN { plan skip_all => "these tests only run in CI" unless $ENV{CI} && $ENV{CIRCLE_JOB} eq 'test_bmo' }; + +use ok DRIVER; + +my $ADMIN_LOGIN = $ENV{BZ_TEST_ADMIN} // 'admin@mozilla.bugs'; +my $ADMIN_PW_OLD = $ENV{BZ_TEST_ADMIN_PASS} // 'passWord1234!'; +my $ADMIN_PW_NEW = $ENV{BZ_TEST_ADMIN_NEWPASS} // 'she7Ka8t'; + +my @require_env = qw( + BZ_BASE_URL + BZ_TEST_NEWBIE + BZ_TEST_NEWBIE_PASS +); + +if (DRIVER =~ /Remote/) { + push @require_env, qw( TWD_HOST TWD_PORT ); +} +my @missing_env = grep { ! exists $ENV{$_} } @require_env; +BAIL_OUT("Missing env: @missing_env") if @missing_env; + +eval { + my $sel = DRIVER->new(base_url => $ENV{BZ_BASE_URL}); + $sel->set_implicit_wait_timeout(600); + + login_ok($sel, $ADMIN_LOGIN, $ADMIN_PW_OLD); + + change_password($sel, $ADMIN_PW_OLD, 'newpassword', 'newpassword2'); + $sel->title_is("Passwords Don't Match"); + $sel->body_text_contains('The two passwords you entered did not match.'); + + change_password($sel, $ADMIN_PW_OLD . "x", "newpassword2", "newpassword2"); + $sel->title_is("Incorrect Old Password"); + + change_password($sel, $ADMIN_PW_OLD, "password", "password"); + $sel->title_is("Password Fails Requirements"); + + change_password($sel, $ADMIN_PW_OLD, $ADMIN_PW_NEW, $ADMIN_PW_NEW); + $sel->title_is("User Preferences"); + logout_ok($sel); + + login_ok($sel, $ADMIN_LOGIN, $ADMIN_PW_NEW); + + # we don't protect against password re-use + change_password($sel, $ADMIN_PW_NEW, $ADMIN_PW_OLD, $ADMIN_PW_OLD); + $sel->title_is("User Preferences"); + logout_ok($sel); + + login_ok($sel, $ENV{BZ_TEST_NEWBIE}, $ENV{BZ_TEST_NEWBIE_PASS}); + + $sel->get_ok("/editusers.cgi"); + $sel->title_is("Authorization Required"); + logout_ok($sel); + + login_ok($sel, $ADMIN_LOGIN, $ADMIN_PW_OLD); + + toggle_require_password_change($sel, $ENV{BZ_TEST_NEWBIE}); + logout_ok($sel); + + login($sel, $ENV{BZ_TEST_NEWBIE}, $ENV{BZ_TEST_NEWBIE_PASS}); + $sel->title_is('Password change required'); + click_and_type($sel, "old_password", $ENV{BZ_TEST_NEWBIE_PASS}); + click_and_type($sel, "new_password1", "password"); + click_and_type($sel, "new_password2", "password"); + submit($sel, '//input[@id="submit"]'); + $sel->title_is('Password Fails Requirements'); + + $sel->go_back_ok(); + $sel->title_is('Password change required'); + click_and_type($sel, "old_password", $ENV{BZ_TEST_NEWBIE_PASS}); + click_and_type($sel, "new_password1", "!!" . $ENV{BZ_TEST_NEWBIE_PASS}); + click_and_type($sel, "new_password2", "!!" . $ENV{BZ_TEST_NEWBIE_PASS}); + submit($sel, '//input[@id="submit"]'); + $sel->title_is('Password Changed'); + change_password( + $sel, + "!!" . $ENV{BZ_TEST_NEWBIE_PASS}, + $ENV{BZ_TEST_NEWBIE_PASS}, + $ENV{BZ_TEST_NEWBIE_PASS} + ); + $sel->title_is("User Preferences"); + + $sel->get_ok("/userprefs.cgi?tab=account"); + $sel->title_is("User Preferences"); + click_link($sel, "I forgot my password"); + $sel->body_text_contains( + ["A token for changing your password has been emailed to you.", + "Follow the instructions in that email to change your password."], + ); + my $token = get_token(); + ok($token, "got a token from resetting password"); + $sel->get_ok("/token.cgi?t=$token&a=cfmpw"); + $sel->title_is('Change Password'); + click_and_type($sel, "password", "nopandas"); + click_and_type($sel, "matchpassword", "nopandas"); + submit($sel, '//input[@id="update"]'); + $sel->title_is('Password Fails Requirements'); + $sel->go_back_ok(); + $sel->title_is('Change Password'); + click_and_type($sel, "password", '??' . $ENV{BZ_TEST_NEWBIE_PASS}); + click_and_type($sel, "matchpassword", '??' . $ENV{BZ_TEST_NEWBIE_PASS}); + submit($sel, '//input[@id="update"]'); + $sel->title_is('Password Changed'); + $sel->get_ok("/token.cgi?t=$token&a=cfmpw"); + $sel->title_is('Token Does Not Exist'); + $sel->get_ok("/login"); + $sel->title_is('Log in to Bugzilla'); + login_ok($sel, $ENV{BZ_TEST_NEWBIE}, "??" . $ENV{BZ_TEST_NEWBIE_PASS}); + change_password( + $sel, + "??" . $ENV{BZ_TEST_NEWBIE_PASS}, + $ENV{BZ_TEST_NEWBIE_PASS}, + $ENV{BZ_TEST_NEWBIE_PASS} + ); + $sel->title_is("User Preferences"); + + logout_ok($sel); + open my $fh, '>', '/app/data/mailer.testfile'; + close $fh; + + $sel->get('/createaccount.cgi'); + $sel->title_is('Create a new Bugzilla account'); + click_and_type($sel, 'login', $ENV{BZ_TEST_NEWBIE2}); + $sel->find_element('//input[@id="etiquette"]', 'xpath')->click(); + submit($sel, '//input[@value="Create Account"]'); + $sel->title_is("Request for new user account '$ENV{BZ_TEST_NEWBIE2}' submitted"); + my ($create_token) = search_mailer_testfile( + qr{/token\.cgi\?t=([^&]+)&a=request_new_account}xs + ); + $sel->get("/token.cgi?t=$create_token&a=request_new_account"); + click_and_type($sel, 'passwd1', $ENV{BZ_TEST_NEWBIE2_PASS}); + click_and_type($sel, 'passwd2', $ENV{BZ_TEST_NEWBIE2_PASS}); + submit($sel, '//input[@value="Create"]'); + + $sel->title_is('Bugzilla Main Page'); + $sel->body_text_contains( + ["The user account $ENV{BZ_TEST_NEWBIE2} has been created", + "successfully"] + ); +}; +if ($@) { + fail("got exception $@"); +} +done_testing(); + +sub submit { + my ($sel, $xpath) = @_; + $sel->find_element($xpath, 'xpath')->submit(); +} + +sub get_token { + my $token; + my $count = 0; + do { + sleep 1 if $count++; + open my $fh, '<', '/app/data/mailer.testfile'; + my $content = do { + local $/ = undef; + <$fh>; + }; + ($token) = $content =~ m!/token\.cgi\?t=3D([^&]+)&a=3Dcfmpw!s; + close $fh; + } until $token || $count > 60; + return $token; +} + +sub search_mailer_testfile { + my ($regexp) = @_; + my $content = ""; + my @result; + my $count = 0; + do { + sleep 1 if $count++; + open my $fh, '<', '/app/data/mailer.testfile'; + $content .= do { + local $/ = undef; + <$fh>; + }; + close $fh; + my $decoded = $content; + $decoded =~ s/\r\n/\n/gs; + $decoded =~ s/=\n//gs; + $decoded =~ s/=([[:xdigit:]]{2})/chr(hex($1))/ges; + @result = $decoded =~ $regexp; + } until @result || $count > 60; + return @result; +} + +sub click_and_type { + my ($sel, $name, $text) = @_; + + eval { + my $el = $sel->find_element(qq{//input[\@name="$name"]}, 'xpath'); + $el->click(); + $sel->send_keys_to_active_element($text); + pass("found $name and typed $text"); + }; + if ($@) { + fail("failed to find $name"); + } +} + +sub click_link { + my ($sel, $text) = @_; + my $el = $sel->find_element($text, 'link_text'); + $el->click(); +} + +sub change_password { + my ($sel, $old, $new1, $new2) = @_; + $sel->get_ok("/userprefs.cgi?tab=account"); + $sel->title_is("User Preferences"); + click_and_type($sel, "old_password", $old); + click_and_type($sel, "new_password1", $new1); + click_and_type($sel, "new_password2", $new2); + submit($sel, '//input[@value="Submit Changes"]'); +} + +sub toggle_require_password_change { + my ($sel, $login) = @_; + $sel->get_ok("/editusers.cgi"); + $sel->title_is("Search users"); + click_and_type($sel, 'matchstr', $login); + submit($sel, '//input[@id="search"]'); + $sel->title_is("Select user"); + click_link($sel, $login); + $sel->find_element('//input[@id="password_change_required"]')->click; + submit($sel, '//input[@id="update"]'); + $sel->title_is("User $login updated"); +} + +sub login { + my ($sel, $login, $password) = @_; + + $sel->get_ok("/login"); + $sel->title_is("Log in to Bugzilla"); + click_and_type($sel, 'Bugzilla_login', $login); + click_and_type($sel, 'Bugzilla_password', $password); + submit($sel, '//input[@name="GoAheadAndLogIn"]'); +} + +sub login_ok { + my ($sel) = @_; + login(@_); + $sel->title_is('Bugzilla Main Page'); +} + +sub logout_ok { + my ($sel) = @_; + $sel->get_ok('/index.cgi?logout=1'); + $sel->title_is("Logged Out"); +} -- cgit v1.2.3-24-g4f1b