From c6a8f843a7288812db8c51448f20d425e40a2142 Mon Sep 17 00:00:00 2001 From: Carlos Salguero Date: Fri, 30 Mar 2018 16:35:08 -0300 Subject: [PATCH] PT-1488 Added MySQL 8 roles support to pt-show-grants --- bin/pt-show-grants | 55 +++++++++++++++++++- lib/Sandbox.pm | 2 +- t/pt-show-grants/mysql_8_roles.t | 89 ++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 t/pt-show-grants/mysql_8_roles.t diff --git a/bin/pt-show-grants b/bin/pt-show-grants index c5e53f92..fa5881fd 100755 --- a/bin/pt-show-grants +++ b/bin/pt-show-grants @@ -1922,9 +1922,25 @@ sub main { my $ignore_users = $o->get('ignore'); my $exit_status = 0; + if (my $roles = get_roles($dbh)) { + print "-- Roles\n"; + my $count=0; + + for my $role (@$roles) { + next if ($o->get("skip-unused-roles") && $role->{active} == 0); + $count++; + printf('CREATE ROLE IF NOT EXISTS `%s`;'."\n", $role->{name}); + } + + if ($count == 0) { + print "No active roles found\n"; + } + print "-- End of roles listing\n"; + } + USER: foreach my $u ( @$users ) { - my $user_host = "'$u->{User}'\@'$u->{Host}'"; + my $user_host = "`$u->{User}`\@`$u->{Host}`"; if ( $ignore_users && $ignore_users->{$user_host} ) { PTDEBUG && _d('Ignoring user', $user_host); next USER; @@ -2084,6 +2100,39 @@ sub parse_user { return ( $user, $host ); } +sub get_roles { + my $dbh = shift; + my $query = <<__EOQ; + SELECT DISTINCT user.user AS name, IF(from_user IS NULL,0, 1) AS active + FROM mysql.user + LEFT JOIN mysql.role_edges ON role_edges.from_user=user.user + WHERE `account_locked`='Y' + AND `password_expired`='Y' + AND `authentication_string`='' +__EOQ + PTDEBUG && _d("Getting roles"); + PTDEBUG && _d($query); + my $roles; + eval { $roles = $dbh->selectall_arrayref($query, { Slice => {} }) }; + if ($EVAL_ERROR) { + warn "Cannot list roles: $EVAL_ERROR"; + PTDEBUG && _d("Cannot list roles: $EVAL_ERROR"); + } + return $roles; +} + +sub is_role { + my ($users, $grant) = @_; + foreach my $u ( @$users ) { + my $user_host = "`$u->{User}`\@`$u->{Host}`"; + warn "> user_host: $user_host"; + if ($grant eq $user_host) { + return 1; + } + } + return 0; +} + sub split_grants { my ($grants) = @_; return unless $grants; @@ -2346,6 +2395,10 @@ example, specifying C<--set-vars wait_timeout=500> overrides the defaultvalue of The tool prints a warning and continues if a variable cannot be set. +=item --skip-unused-roles + +When dumping MySQL 8+ roles, skip unused roles. + =item --socket short form: -S; type: string diff --git a/lib/Sandbox.pm b/lib/Sandbox.pm index 474077e7..6ecd069e 100644 --- a/lib/Sandbox.pm +++ b/lib/Sandbox.pm @@ -391,7 +391,7 @@ sub verify_test_data { my @diffs; foreach my $c ( @checksums ) { next unless $c->{checksum}; - if ( $c->{checksum} ne $ref->{$c->{table}}->{checksum} ) { + if ( $ref->{$c->{table}} && $c->{checksum} ne $ref->{$c->{table}}->{checksum} ) { push @diffs, $c->{table}; } } diff --git a/t/pt-show-grants/mysql_8_roles.t b/t/pt-show-grants/mysql_8_roles.t new file mode 100644 index 00000000..5f787628 --- /dev/null +++ b/t/pt-show-grants/mysql_8_roles.t @@ -0,0 +1,89 @@ +#!/usr/bin/env perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +use PerconaTest; +use Sandbox; +use SqlModes; +require "$trunk/bin/pt-show-grants"; + +my $dp = new DSNParser(opts=>$dsn_opts); +my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp); +my $dbh = $sb->get_dbh_for('master'); + +if ( !$dbh ) { + plan skip_all => 'Cannot connect to sandbox master'; +} elsif ($sandbox_version < '8.0') { + plan skip_all => "There are no roles in this MySQL version. Need MySQL 8.0+"; +} else { + plan tests => 4; +} + +$sb->wipe_clean($dbh); + +my $setup_queries = [ + "CREATE ROLE IF NOT EXISTS 'app_developer', 'app_read', 'app_write', 'tester';", + "GRANT ALL ON test.* TO 'app_developer';", + "GRANT SELECT ON test.* TO 'app_read';", + "GRANT SELECT ON test.* TO 'tester';", + "CREATE USER 'zapp'\@'localhost' IDENTIFIED WITH 'mysql_native_password' BY 'test1234';", + "GRANT 'app_read','app_write' TO 'zapp'\@'localhost';", + "GRANT 'tester' TO 'zapp'\@'localhost' WITH ADMIN OPTION;", + "FLUSH PRIVILEGES", +]; + +my $cleanup_queries = [ + "DROP USER IF EXISTS 'zapp'\@'localhost'", + "DROP ROLE IF EXISTS 'app_developer', 'app_read', 'app_write', 'tester'", + "FLUSH PRIVILEGES", +]; +for my $query(@$setup_queries) { + $sb->do_as_root('master', $query); +} + +my $output; +my $cnf = '/tmp/12345/my.sandbox.cnf'; + +eval { + $output = output( + sub { pt_show_grants::main('-F', $cnf, '--skip-unused-roles'); } + ); +}; +is( + $EVAL_ERROR, + '', + 'Does not die on anonymous user (issue 445)', +); + +like( + $output, + qr/CREATE ROLE IF NOT EXISTS `app_read`;/, + 'Roles has been created' +) or diag($output); + +unlike( + $output, + qr/CREATE ROLE IF NOT EXISTS `app_developer`;/, + 'Unused roles has been skipped' + +); + +for my $query(@$cleanup_queries) { + $sb->do_as_root('master', $query); +} + +# ############################################################################# +# Done. +# ############################################################################# +$sb->wipe_clean($dbh); +ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); +exit;