From c2815ec3127c703889d2d89f5b102a99c803448e Mon Sep 17 00:00:00 2001 From: Carlos Salguero Date: Thu, 26 Apr 2018 17:20:42 -0300 Subject: [PATCH] PT-1546 Improved MySQL 8 roles support This fix adds these improvements: - Only used roles are being dumped. - Do no treat roles as regular users so, using --drop won't affect roles. - If --only=user is specified, only roles for that user will be dumped. - Specifying --only=role,user will ignore role (as per item #2) so the role will be dumped ONLY if it was granted to 'user' - Added new parameter --[no]include-unused-roles Tags: pt-show-grants Resolves: PT-1525 See also: PT-1488 --- Changelog | 6 ++-- bin/pt-show-grants | 68 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Changelog b/Changelog index 2599015e..902b1d5e 100644 --- a/Changelog +++ b/Changelog @@ -1,8 +1,10 @@ Changelog for Percona Toolkit -v3.0.9 released 2018-04-17 +v3.0.10 -v3.0.9 + * Improvement PT-1546 : Improved support of MySQL 8 roles + +v3.0.9 released 2018-04-17 * Feature PT-1530 : Add support for encryption status to mysql-summary * Fixed bug PT-1527 : pt-table-checksum ignores --nocheck-binlog-format diff --git a/bin/pt-show-grants b/bin/pt-show-grants index 0fdda5e4..bff296cf 100755 --- a/bin/pt-show-grants +++ b/bin/pt-show-grants @@ -1909,8 +1909,14 @@ sub main { . ($o->get('timestamp') ? ", MySQL $version at $ts" : ", MySQL $version"), ), "\n" if $o->get('header'); + # MySQL 8 roles must be excluded from the regular users list. + # Roles can be identified because the user password is expired, the authentication + # string is empty and the account is locked my $users = $o->get('only') || $dbh->selectall_arrayref( - 'SELECT DISTINCT User, Host FROM mysql.user ORDER BY User, Host', + 'SELECT DISTINCT User, Host FROM mysql.user WHERE NOT (`account_locked`="Y" + AND `password_expired`="Y" + AND `authentication_string`="" + ) ORDER BY User, Host', { Slice => {} }); if ( scalar @all_hosts ) { my $where = join(' OR ', map { "User='$_'" } @all_hosts); @@ -1922,6 +1928,24 @@ sub main { my $ignore_users = $o->get('ignore'); my $exit_status = 0; + my $roles = get_roles($dbh, $users); + if ($roles && scalar @$roles > 0) { + print "-- Roles\n"; + my $count=0; + + for my $role (@$roles) { + next if (!$o->get("include-unused-roles") && $role->{active} == 0); + unshift @$users, { Host => $role->{host}, User => $role->{name}, IsRole => 1}; + $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}'"; @@ -2040,7 +2064,7 @@ sub main { "\n"; } - if ( $o->get('drop') ) { + if ( $o->get('drop') && !defined($u->{IsRole}) ) { print join("\n", "DROP USER $user_host;", "DELETE FROM `mysql`.`user` WHERE `User`='$u->{User}' AND `Host`='$u->{Host}';", @@ -2084,6 +2108,42 @@ sub parse_user { return ( $user, $host ); } +sub get_roles { + my ($dbh, $users) = @_; + my $query = <<__EOQ; + SELECT DISTINCT user.user AS name, user.host, 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 + if (scalar $users > 0) { + my $user_names = join (", ", map { "'$_->{User}'" } @$users); + $query .= " AND to_user IN ($user_names)"; + } + PTDEBUG && _d("Getting roles"); + PTDEBUG && _d($query); + my $roles; + eval { $roles = $dbh->selectall_arrayref($query, { Slice => {} }) }; + if ($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 +2406,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 --[no]include-unused-roles + +When dumping MySQL 8+ roles, include unused roles. + =item --socket short form: -S; type: string