File Coverage

blib/lib/MySQL/GrantParser.pm
Criterion Covered Total %
statement 69 103 66.9
branch 26 40 65.0
condition 0 7 0.0
subroutine 10 12 83.3
pod 2 6 33.3
total 107 168 63.6


line stmt bran cond sub pod time code
1             package MySQL::GrantParser;
2              
3 4     4   50453 use strict;
  4         7  
  4         162  
4 4     4   18 use warnings;
  4         6  
  4         133  
5 4     4   126 use 5.008_005;
  4         17  
6              
7             our $VERSION = '1.003';
8              
9 4     4   6604 use DBI;
  4         70260  
  4         313  
10 4     4   42 use Carp;
  4         5  
  4         4664  
11              
12             sub new {
13 1     1 1 630 my($class, %args) = @_;
14              
15 1         4 my $self = {
16             dbh => undef,
17             need_disconnect => 0,
18             };
19 1 50       5 if (exists $args{dbh}) {
20 1         3 $self->{dbh} = delete $args{dbh};
21             } else {
22 0 0 0     0 if (!$args{hostname} && !$args{socket}) {
23 0         0 Carp::croak("missing mandatory args: hostname or socket");
24             }
25              
26 0         0 my $dsn = "DBI:mysql:";
27 0         0 for my $p (
28             [qw(hostname hostname)],
29             [qw(port port)],
30             [qw(socket mysql_socket)],
31             ) {
32 0         0 my $arg_key = $p->[0];
33 0         0 my $param_key = $p->[1];
34 0 0       0 if ($args{$arg_key}) {
35 0         0 $dsn .= ";$param_key=$args{$arg_key}";
36             }
37             }
38              
39 0         0 $self->{need_disconnect} = 1;
40             $self->{dbh} = DBI->connect(
41             $dsn,
42             $args{user}||'',
43 0 0 0     0 $args{password}||'',
      0        
44             {
45             AutoCommit => 0,
46             },
47             ) or Carp::croak("$DBI::errstr ($DBI::err)");
48             }
49              
50 1 50       5 $self->{server_version} = exists $self->{dbh}->{mysql_serverversion} ? $self->{dbh}->{mysql_serverversion} : 0;
51              
52 1         4 return bless $self, $class;
53             }
54              
55             sub parse {
56 0     0 1 0 my $self = shift;
57 0         0 my %grants;
58              
59             # select all user
60 0         0 my $rset = $self->{dbh}->selectall_arrayref('SELECT user, host FROM mysql.user');
61              
62 0         0 for my $user_host (@$rset) {
63 0         0 my ($user, $host) = @{$user_host};
  0         0  
64 0         0 my $quoted_user_host = $self->quote_user($user, $host);
65 0         0 my $rset = $self->{dbh}->selectall_arrayref("SHOW GRANTS FOR ${quoted_user_host}");
66 0         0 my @stmts;
67 0         0 for my $rs (@$rset) {
68 0         0 push @stmts, @{$rs};
  0         0  
69             }
70 0 0       0 if ($self->{server_version} >= 50706) {
71             # As of MySQL 5.7.6, SHOW GRANTS output does not include IDENTIFIED BY PASSWORD clauses. Use the SHOW CREATE USER statement instead.
72             # https://dev.mysql.com/doc/refman/5.7/en/show-grants.html
73 0         0 my $rset = $self->{dbh}->selectall_arrayref("SHOW CREATE USER ${quoted_user_host}");
74 0         0 for my $rs (@$rset) {
75 0         0 push @stmts, @{$rs};
  0         0  
76             }
77             }
78              
79 0         0 %grants = (%grants, %{ parse_stmts(\@stmts) });
  0         0  
80             }
81              
82 0         0 return \%grants;
83             }
84              
85             sub parse_stmts {
86 7     7 0 5456 my $stmts = shift;
87 7         11 my @grants = ();
88 7         12 for my $stmt (@$stmts) {
89 14         48 my $parsed = {
90             with => '',
91             require => '',
92             identified => '',
93             privs => [],
94             object => '',
95             user => '',
96             host => '',
97             };
98              
99 14 100       44 if ($stmt =~ s/\s+IDENTIFIED WITH\s+'([^']+)'\s+AS\s+(.+?)\s+//) {
100             # my $auth_plugin = $1; # eg: mysql_native_password
101 1         3 $parsed->{identified} = "PASSWORD $2";
102             }
103 14 100       34 if ($stmt =~ s/\s+IDENTIFIED WITH\s+'([^']+)'\s+//) {
104             # no AS
105 1         1 $parsed->{identified} = "";
106             }
107 14 100       34 if ($stmt =~ /\ACREATE\s+USER\s+'(.*)'\@'(.+)'/) {
108 2         3 $parsed->{user} = $1;
109 2         2 $parsed->{host} = $2;
110             }
111              
112 14 100       54 if ($stmt =~ s/\s+WITH\s+(.+?)\z//) {
113 6         13 $parsed->{with} = $1;
114             }
115 14 100       48 if ($stmt =~ s/\s+REQUIRE\s+(.+?)\z//) {
116 2         4 $parsed->{require} = $1;
117             }
118 14 100       38 if ($stmt =~ s/\s+IDENTIFIED BY\s+(.+?)\z//) {
119 3         7 $parsed->{identified} = $1;
120             }
121 14 100       66 if ($stmt =~ /\AGRANT\s+(.+?)\s+ON\s+(.+?)\s+TO\s+'(.*)'\@'(.+)'\z/) {
122 12         16 $parsed->{privs} = parse_privs($1);
123 12         17 $parsed->{object} = $2;
124 12         17 $parsed->{user} = $3;
125 12         15 $parsed->{host} = $4;
126             }
127              
128 14         18 push @grants, $parsed;
129             }
130              
131 7         12 return pack_grants(@grants);
132             }
133              
134             sub pack_grants {
135 7     7 0 11 my @grants = @_;
136 7         5 my $packed;
137              
138 7         10 for my $grant (@grants) {
139 14         17 my $user = delete $grant->{user};
140 14         14 my $host = delete $grant->{host};
141 14         22 my $user_host = join '@', $user, $host;
142 14         14 my $object = delete $grant->{object};
143 14         14 my $identified = delete $grant->{identified};
144 14         16 my $required = delete $grant->{require};
145              
146 14 100       23 unless (exists $packed->{$user_host}) {
147 7         25 $packed->{$user_host} = {
148             user => $user,
149             host => $host,
150             objects => {},
151             options => {
152             required => '',
153             identified => '',
154             },
155             };
156             }
157 14 50       11 $packed->{$user_host}{objects}{$object} = $grant if (scalar(@{ $grant->{privs} || []}) > 0);
  14 100       40  
158 14 100       20 $packed->{$user_host}{options}{required} = $required if $required;
159              
160 14 100       35 if ($identified) {
161 4         22 $packed->{$user_host}{options}{identified} = $identified;
162             }
163             }
164              
165 7         27 return $packed;
166             }
167              
168             sub quote_user {
169 0     0 0 0 my $self = shift;
170 0         0 my($user, $host) = @_;
171 0         0 sprintf q{%s@%s}, $self->{dbh}->quote($user), $self->{dbh}->quote($host);
172             }
173              
174             sub parse_privs {
175 12     12 0 20 my $privs = shift;
176 12         9 $privs .= ',';
177              
178 12         11 my @priv_list = ();
179              
180 12         49 while ($privs =~ /\G([^,(]+(?:\([^)]+\))?)\s*,\s*/g) {
181 26         69 push @priv_list, $1;
182             }
183              
184 12         24 return \@priv_list;
185             }
186              
187             sub DESTROY {
188 1     1   1156 my $self = shift;
189 1 50       50 if ($self->{need_disconnect}) {
190 0 0         $self->{dbh} && $self->{dbh}->disconnect;
191             }
192             }
193              
194             1;
195              
196             __END__