diff --git a/bin/pt-show-grants b/bin/pt-show-grants index 69be9346..751b8556 100755 --- a/bin/pt-show-grants +++ b/bin/pt-show-grants @@ -16,6 +16,7 @@ BEGIN { OptionParser DSNParser Daemon + VersionCompare )); } @@ -1727,6 +1728,53 @@ sub _d { # End Daemon package # ########################################################################### +# ########################################################################### +# VersionCompare package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/VersionCompare.pm +# t/lib/VersionCompare.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package VersionCompare; + +use strict; +use English qw(-no_match_vars); +use constant PTDEBUG => $ENV{PTDEBUG} || 0; + +sub cmp { + my ($v1, $v2) = @_; + + $v1 =~ s/[^\d\.]//; + $v2 =~ s/[^\d\.]//; + + my @a = ( $v1 =~ /(\d+)\.?/g ); + my @b = ( $v2 =~ /(\d+)\.?/g ); + foreach my $n1 (@a) { + $n1 += 0; + if (!@b) { + return 1; + } + my $n2 = shift @b; + $n2 += 0; + if ($n1 == $n2) { + next; + } + else { + return $n1 <=> $n2; + } + } + return @b ? -1 : 0; +} + + +1; +} +# ########################################################################### +# End VersionCompare package +# ########################################################################### + # ########################################################################### # This is a combination of modules and programs in one -- a runnable module. # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last @@ -1826,6 +1874,7 @@ sub main { { AutoCommit => 1, }); my ( $version, $ts ) = $dbh->selectrow_array("SELECT VERSION(), NOW()"); + print join("\n", "-- Grants dumped by pt-show-grants", "-- Dumped from server " . ($dbh->{mysql_hostinfo} || '') @@ -1856,6 +1905,27 @@ sub main { PTDEBUG && _d('Checking user', $user_host); } + # If MySQL 5.7.6+ then we need to use SHOW CREATE USER + my @create_user; + if ( VersionCompare::cmp($version, '5.7.6') >= 0 ) { + eval { + @create_user = @{ $dbh->selectcol_arrayref("SHOW CREATE USER $user_host") }; + }; + if ( $EVAL_ERROR ) { + PTDEBUG && _d($EVAL_ERROR); + $exit_status = 1; + } + PTDEBUG && _d('CreateUser:', Dumper(\@create_user)); + # make this replication safe converting the CREATE USER into + # CREATE USER IF NOT EXISTS and then doing an ALTER USER + my $create = $create_user[0]; + my $alter = $create; + $create =~ s{CREATE USER}{CREATE USER IF NOT EXISTS}; + $create =~ s{ IDENTIFIED .*}{}; + $alter =~ s{CREATE USER}{ALTER USER}; + @create_user = ( $create, $alter ); + PTDEBUG && _d('AdjustedCreateUser:', Dumper(\@create_user)); + } my @grants; eval { @grants = @{ $dbh->selectcol_arrayref("SHOW GRANTS FOR $user_host") }; @@ -1902,6 +1972,9 @@ sub main { @grants = sort { $b =~ m/IDENTIFIED BY/ <=> $a =~ m/IDENTIFIED BY/ || $a cmp $b } @grants; + + # Add @create_user if there + @grants = ( @create_user, @grants ) if scalar @create_user > 0; PTDEBUG && _d('Grants sorted:', Dumper(\@grants)); # Print REVOKE statements. @@ -1929,7 +2002,7 @@ sub main { } @result; - } @grants; + } grep { /\bgrant\b/i } @grants; print join( "\n", @@ -2007,6 +2080,7 @@ sub split_grants { return @grants; } + sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } diff --git a/lib/VersionCompare.pm b/lib/VersionCompare.pm new file mode 100644 index 00000000..93b3f16b --- /dev/null +++ b/lib/VersionCompare.pm @@ -0,0 +1,68 @@ +# This program is copyright 2016 Percona LLC. +# Feedback and improvements are welcome. +# +# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar +# systems, you can issue `man perlgpl' or `man perlartistic' to read these +# licenses. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place, Suite 330, Boston, MA 02111-1307 USA. +# ########################################################################### +# VersionCompare package +# ########################################################################### + +# The purpose of this very simple module is to compare MySQL version strings +# There's VersionParser and the perl core "version" module, but I wanted +# something simpler and that could grow incrementally + +{ +package VersionCompare; + +use strict; +use English qw(-no_match_vars); +use constant PTDEBUG => $ENV{PTDEBUG} || 0; + +sub cmp { + my ($v1, $v2) = @_; + + # Remove all but numbers and dots. + # Assume simple 1.2.3 style + $v1 =~ s/[^\d\.]//; + $v2 =~ s/[^\d\.]//; + + my @a = ( $v1 =~ /(\d+)\.?/g ); + my @b = ( $v2 =~ /(\d+)\.?/g ); + foreach my $n1 (@a) { + $n1 += 0; #convert to number + if (!@b) { + # b ran out of digits, a is larger + return 1; + } + my $n2 = shift @b; + $n2 += 0; # convert to number + if ($n1 == $n2) { + # still tied?, fetch next + next; + } + else { + # difference! return result + return $n1 <=> $n2; + } + } + # b still has digits? it's larger, else it's a tie + return @b ? -1 : 0; +} + + +1; +} +# ########################################################################### +# End VersionCompare package +# ########################################################################### diff --git a/t/lib/VersionCompare.t b/t/lib/VersionCompare.t new file mode 100644 index 00000000..bdb31a06 --- /dev/null +++ b/t/lib/VersionCompare.t @@ -0,0 +1,48 @@ +#!/usr/bin/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 tests=>14; + +use VersionCompare; +use PerconaTest; + +my @versions = qw( 5.7 5.6 1 + 5.6 5.7 -1 + 5.6 5.6 0 + 5.17 5.6 1 + 5.9 5.17 -1 + 5.10 5.10 0 + 5.1.2 5.5 -1 + 5 3 1 + 5.6 5.5.5 1 + 5.7.7 5.7.7 0 + 5.6 5.6.0 -1 + v5.4.3-0 5.7 -1 + 5.7 v5.4.3-0 1 + v5.7.3-0 v5.4.3-0 1 + ); + +while ( @versions ) { + my $v1 = shift @versions; + my $v2 = shift @versions; + my $res = shift @versions; + + ok ( VersionCompare::cmp($v1, $v2) == $res, + "$v1 vs $v2" + ) or diag("result was [",VersionCompare::cmp($v1, $v2),"]"); +} + + +# ############################################################################# +# Done. +# ############################################################################# +#ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox"); +exit; diff --git a/t/pt-show-grants/all_grants.t b/t/pt-show-grants/all_grants.t index 2b886623..c4c8cb26 100644 --- a/t/pt-show-grants/all_grants.t +++ b/t/pt-show-grants/all_grants.t @@ -49,29 +49,64 @@ $modes->restore_original_modes; $output = output( sub { pt_show_grants::main('-F', $cnf, qw(--only bob --no-header)); } ); + +my $expected_57 = <<'END_OUTPUT_1'; +-- Grants for 'bob'@'%' +CREATE USER IF NOT EXISTS 'bob'@'%'; +ALTER USER 'bob'@'%' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT USAGE ON *.* TO 'bob'@'%'; +-- Grants for 'bob'@'192.168.1.1' +CREATE USER IF NOT EXISTS 'bob'@'192.168.1.1'; +ALTER USER 'bob'@'192.168.1.1' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT USAGE ON *.* TO 'bob'@'192.168.1.1'; +-- Grants for 'bob'@'localhost' +CREATE USER IF NOT EXISTS 'bob'@'localhost'; +ALTER USER 'bob'@'localhost' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT USAGE ON *.* TO 'bob'@'localhost'; +END_OUTPUT_1 + +my $expected_56 = <<'END_OUTPUT_2'; +-- Grants for 'bob'@'%' +GRANT USAGE ON *.* TO 'bob'@'%'; +-- Grants for 'bob'@'192.168.1.1' +GRANT USAGE ON *.* TO 'bob'@'192.168.1.1'; +-- Grants for 'bob'@'localhost' +GRANT USAGE ON *.* TO 'bob'@'localhost'; +END_OUTPUT_2 + +my $expected = $sandbox_version < '5.7' ? $expected_56 : $expected_57; + is( $output, -"-- Grants for 'bob'\@'%' -GRANT USAGE ON *.* TO 'bob'\@'%'; --- Grants for 'bob'\@'192.168.1.1' -GRANT USAGE ON *.* TO 'bob'\@'192.168.1.1'; --- Grants for 'bob'\@'localhost' -GRANT USAGE ON *.* TO 'bob'\@'localhost'; -", + $expected, '--only user gets grants for user on all hosts (issue 551)' ); $output = output( sub { pt_show_grants::main('-F', $cnf, qw(--only bob@192.168.1.1 --no-header)); } ); + +$expected_57 = <<'END_OUTPUT_3'; +-- Grants for 'bob'@'192.168.1.1' +CREATE USER IF NOT EXISTS 'bob'@'192.168.1.1'; +ALTER USER 'bob'@'192.168.1.1' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT USAGE ON *.* TO 'bob'@'192.168.1.1'; +END_OUTPUT_3 + +$expected_56 = <<'END_OUTPUT_4'; +-- Grants for 'bob'@'192.168.1.1' +GRANT USAGE ON *.* TO 'bob'@'192.168.1.1'; +END_OUTPUT_4 + +$expected = $sandbox_version < '5.7' ? $expected_56 : $expected_57; + is( $output, -"-- Grants for 'bob'\@'192.168.1.1' -GRANT USAGE ON *.* TO 'bob'\@'192.168.1.1'; -", + $expected, '--only user@host' ); + diag(`/tmp/12345/use -u root -e "DROP USER 'bob'\@'%'"`); diag(`/tmp/12345/use -u root -e "DROP USER 'bob'\@'localhost'"`); diag(`/tmp/12345/use -u root -e "DROP USER 'bob'\@'192.168.1.1'"`); diff --git a/t/pt-show-grants/basics.t b/t/pt-show-grants/basics.t index 619a375a..0ac60a70 100644 --- a/t/pt-show-grants/basics.t +++ b/t/pt-show-grants/basics.t @@ -102,49 +102,56 @@ $modes->del('NO_AUTO_CREATE_USER'); diag(`/tmp/12345/use -u root -e "GRANT SELECT(DateCreated, PckPrice, PaymentStat, SANumber) ON test.t TO 'sally'\@'%'"`); diag(`/tmp/12345/use -u root -e "GRANT SELECT(city_id), INSERT(city) ON sakila.city TO 'sally'\@'%'"`); $modes->restore_original_modes(); + +my $postfix = $sandbox_version < '5.7' ? '' : '-57'; + ok( no_diff( sub { pt_show_grants::main('-F', $cnf, qw(--only sally --no-header)) }, - "t/pt-show-grants/samples/column-grants.txt", + "t/pt-show-grants/samples/column-grants$postfix.txt", stderr => 1, ), "Column-level grants (bug 866075)" ); + ok( no_diff( sub { pt_show_grants::main('-F', $cnf, qw(--only sally --no-header), qw(--separate)) }, - "t/pt-show-grants/samples/column-grants-separate.txt", + "t/pt-show-grants/samples/column-grants-separate$postfix.txt", stderr => 1, - ), + ), "Column-level grants --separate (bug 866075)" ); + ok( no_diff( sub { pt_show_grants::main('-F', $cnf, qw(--only sally --no-header), qw(--separate --revoke)) }, - "t/pt-show-grants/samples/column-grants-separate-revoke.txt", + "t/pt-show-grants/samples/column-grants-separate-revoke$postfix.txt", stderr => 1, ), "Column-level grants --separate --revoke (bug 866075)" ); + diag(`/tmp/12345/use -u root -e "GRANT SELECT ON sakila.city TO 'sally'\@'%'"`); ok( no_diff( sub { pt_show_grants::main('-F', $cnf, qw(--only sally --no-header)) }, - "t/pt-show-grants/samples/column-grants-combined.txt", + "t/pt-show-grants/samples/column-grants-combined$postfix.txt", stderr => 1, + keep_output => 1, ), "Column-level grants combined with table-level grants on the same table (bug 866075)" ); + diag(`/tmp/12345/use -u root -e "DROP USER 'sally'\@'%'"`); -DONE: # ############################################################################# # Done. # ############################################################################# diff --git a/t/pt-show-grants/issue_445.t b/t/pt-show-grants/issue_445.t index 36244734..54ae405a 100644 --- a/t/pt-show-grants/issue_445.t +++ b/t/pt-show-grants/issue_445.t @@ -63,7 +63,7 @@ like( $output, qr/REVOKE USAGE ON \*\.\* FROM ''\@'';/, 'Prints revoke for anonymous user (issue 445)' -); +) or diag($output); diag(`/tmp/12345/use -u root -e "DROP USER ''\@''"`); $output = `/tmp/12345/use -e "SELECT user FROM mysql.user WHERE user = ''"`; diff --git a/t/pt-show-grants/samples/column-grants-57.txt b/t/pt-show-grants/samples/column-grants-57.txt new file mode 100644 index 00000000..9980134f --- /dev/null +++ b/t/pt-show-grants/samples/column-grants-57.txt @@ -0,0 +1,6 @@ +-- Grants for 'sally'@'%' +CREATE USER IF NOT EXISTS 'sally'@'%'; +ALTER USER 'sally'@'%' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT INSERT (city), SELECT (city_id) ON `sakila`.`city` TO 'sally'@'%'; +GRANT SELECT (SANumber, DateCreated, PaymentStat, PckPrice) ON `test`.`t` TO 'sally'@'%'; +GRANT USAGE ON *.* TO 'sally'@'%'; diff --git a/t/pt-show-grants/samples/column-grants-combined-57.txt b/t/pt-show-grants/samples/column-grants-combined-57.txt new file mode 100644 index 00000000..edecce0b --- /dev/null +++ b/t/pt-show-grants/samples/column-grants-combined-57.txt @@ -0,0 +1,6 @@ +-- Grants for 'sally'@'%' +CREATE USER IF NOT EXISTS 'sally'@'%'; +ALTER USER 'sally'@'%' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT INSERT (city), SELECT, SELECT (city_id) ON `sakila`.`city` TO 'sally'@'%'; +GRANT SELECT (SANumber, DateCreated, PaymentStat, PckPrice) ON `test`.`t` TO 'sally'@'%'; +GRANT USAGE ON *.* TO 'sally'@'%'; diff --git a/t/pt-show-grants/samples/column-grants-separate-57.txt b/t/pt-show-grants/samples/column-grants-separate-57.txt new file mode 100644 index 00000000..02bfe818 --- /dev/null +++ b/t/pt-show-grants/samples/column-grants-separate-57.txt @@ -0,0 +1,7 @@ +-- Grants for 'sally'@'%' +CREATE USER IF NOT EXISTS 'sally'@'%'; +ALTER USER 'sally'@'%' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT INSERT (city) ON `sakila`.`city` TO 'sally'@'%'; +GRANT SELECT (SANumber, DateCreated, PaymentStat, PckPrice) ON `test`.`t` TO 'sally'@'%'; +GRANT SELECT (city_id) ON `sakila`.`city` TO 'sally'@'%'; +GRANT USAGE ON *.* TO 'sally'@'%'; diff --git a/t/pt-show-grants/samples/column-grants-separate-revoke-57.txt b/t/pt-show-grants/samples/column-grants-separate-revoke-57.txt new file mode 100644 index 00000000..083ad358 --- /dev/null +++ b/t/pt-show-grants/samples/column-grants-separate-revoke-57.txt @@ -0,0 +1,12 @@ +-- Revoke statements for 'sally'@'%' +REVOKE INSERT (city) ON `sakila`.`city` FROM 'sally'@'%'; +REVOKE SELECT (SANumber, DateCreated, PaymentStat, PckPrice) ON `test`.`t` FROM 'sally'@'%'; +REVOKE SELECT (city_id) ON `sakila`.`city` FROM 'sally'@'%'; +REVOKE USAGE ON *.* FROM 'sally'@'%'; +-- Grants for 'sally'@'%' +CREATE USER IF NOT EXISTS 'sally'@'%'; +ALTER USER 'sally'@'%' IDENTIFIED WITH 'mysql_native_password' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; +GRANT INSERT (city) ON `sakila`.`city` TO 'sally'@'%'; +GRANT SELECT (SANumber, DateCreated, PaymentStat, PckPrice) ON `test`.`t` TO 'sally'@'%'; +GRANT SELECT (city_id) ON `sakila`.`city` TO 'sally'@'%'; +GRANT USAGE ON *.* TO 'sally'@'%';