mysqlaudit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjRmYmMyZjFiYjNiMGNkYzllNTY3NjZjMWJlZTljNjY0MTUwOTE1Zg==
5
+ data.tar.gz: !binary |-
6
+ N2MwYTg0NWFlZjQ5NzAyMTQzYWRmYzc0YWEwMDk4NGRmY2ViN2ZjNw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ODUzYWIxYWYxY2NhMTI2MTlmZTM0NzhmN2MyYTEyYWY0MjlhMzEwMGUxNWZh
10
+ MjVhMmI3ZDQ1YWVlZTE0YjRjM2E3MTJlZGQxMGFiYjA2YjA4NWQ1NWQyOWIz
11
+ NzQ1MDVmYmM5NWYzNWFkYzEzYWI0YTA1ZjQ5NjhlMDMxYTliNzU=
12
+ data.tar.gz: !binary |-
13
+ MjI4ODI0MWQwODYyNWViZjdiMjFiYWIwZmM0ZmUyOWY2NzVkN2RkYjMyNzYx
14
+ OWZkNTc5NzNiZGFkYWNlYjNkMGJhMjlhMzgwYWFlZTk5NjRjYjI4Nzg5MWU5
15
+ NGY2N2EyYjc0ZTBkNDFlOWEzZWZkZTBkY2M2YzlhMDJiMWY0MzQ=
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nicola Strappazzon C.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Mysqlaudit
2
+
3
+ MySQL tool build in ruby for audit all tables with triggers, this is a change
4
+ log for SQL transactions executed sentences as INSERT, UPDATE, and DELETE.
5
+
6
+ ## Useful for:
7
+
8
+ 1. Log all changes on table.
9
+ 2. Rollback changes.
10
+ 3. Detecting unnecessary transactions.
11
+
12
+ ## Installation
13
+
14
+ Install this tool executing the following command:
15
+
16
+ ```Shell
17
+ $ gem install mysqlaudit
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ This is a simple tutorial to understand this tool, please, follow next
23
+ instructions:
24
+
25
+ ### Install test enviroment
26
+
27
+ Create test database and table:
28
+
29
+ ```SQL
30
+ CREATE DATABASE audit CHARACTER SET utf8;
31
+
32
+ USE audit;
33
+
34
+ CREATE TABLE foo (
35
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
36
+ email char(255) NOT NULL,
37
+ name VARCHAR(45) NOT NULL,
38
+ birthday TIMESTAMP NOT NULL,
39
+ PRIMARY KEY (id),
40
+ UNIQUE KEY id_UNIQUE (id)
41
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
42
+ ```
43
+
44
+ Install audit table and triggers on foo table:
45
+
46
+ ```Shell
47
+ $ mysqlaudit install --host 127.0.0.1 --user root --pass admin --schema audit --table foo
48
+ ```
49
+
50
+ The following query catch all changes of data with trigger audit:
51
+
52
+ ```SQL
53
+ INSERT INTO foo (email, name, birthday) VALUES ('abc@abc.com','Fulano','1980-04-01');
54
+ INSERT INTO foo (email, name, birthday) VALUES ('def@def.org','Mengano','1979-03-28');
55
+ INSERT INTO foo (email, name, birthday) VALUES ('ghi@ghi.net','Jaimito','1980-06-15');
56
+ UPDATE foo SET name = 'Zutano' WHERE id = 3;
57
+ DELETE FROM foo WHERE id = 2;
58
+ ```
59
+
60
+ To apply rollback with specific transaction, this generate SQL output:
61
+
62
+ ### Get deleted data:
63
+
64
+ ```Shell
65
+ $ mysqlaudit rollback --host 127.0.0.1 \
66
+ --user root \
67
+ --pass admin \
68
+ --schema audit \
69
+ --table foo \
70
+ --statement delete
71
+ ```
72
+
73
+ Output of last executed command:
74
+
75
+ ```
76
+ /* 2013-07-11 07:25:25 */ INSERT INTO foo (id, email, name, birthday) VALUES (2, 'def@def.org', 'Mengano', '1979-03-28 00:00:00');
77
+ ```
78
+
79
+ ### Get updated data:
80
+ ```Shell
81
+ $ mysqlaudit rollback --host 127.0.0.1 \
82
+ --user root \
83
+ --pass admin \
84
+ --schema audit \
85
+ --table foo \
86
+ --statement update
87
+ ```
88
+
89
+ Output of last executed command:
90
+
91
+ ```
92
+ /* 2013-07-11 07:26:26 */ UPDATE users SET birthday = '1980-06-15 00:00:00' WHERE id = 3;
93
+ /* 2013-07-11 07:26:26 */ UPDATE users SET name = 'Jaimito' WHERE id = 3;
94
+ ```
95
+
96
+ ### Get inserted data:
97
+ ```Shell
98
+ $ mysqlaudit rollback --host 127.0.0.1 \
99
+ --user root \
100
+ --pass admin \
101
+ --schema audit \
102
+ --table foo \
103
+ --statement insert
104
+ ```
105
+
106
+ Output of last executed command:
107
+
108
+ ```
109
+ /* 2013-07-11 07:27:27 */ DELETE FROM foo WHERE id = 1;
110
+ /* 2013-07-11 07:27:27 */ DELETE FROM foo WHERE id = 2;
111
+ /* 2013-07-11 07:27:27 */ DELETE FROM foo WHERE id = 3;
112
+ ```
113
+
114
+ ### Uninstall test enviroment:
115
+
116
+ Uninstall all triggers and drop audit table.
117
+
118
+ ```Shell
119
+ $ mysqlaudit uninstall --host 127.0.0.1 \
120
+ --user root \
121
+ --pass admin \
122
+ --schema audit \
123
+ --table foo \
124
+ --drop-audit-table
125
+ ```
126
+
127
+ Drop testing table and database:
128
+
129
+ ```SQL
130
+ DROP TABLE audit;
131
+ DROP DATABASE foo;
132
+ ```
133
+
134
+ For more information:
135
+
136
+ ```Shell
137
+ $ mysqlaudit --help
138
+ ```
139
+
140
+ ## Warning
141
+
142
+ 1. Do not use this tool in production before testing it.
143
+ 2. Please, use when do you need.
144
+ 3. The author is NOT responsible for misuse use of this tool.
145
+
146
+ ## Contributing
147
+
148
+ 1. Fork it
149
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
150
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
151
+ 4. Push to the branch (`git push origin my-new-feature`)
152
+ 5. Create new Pull Request
data/bin/mysqlaudit ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Only for deploy this script.
4
+ $LOAD_PATH << './lib'
5
+
6
+
7
+ require 'rubygems'
8
+ require 'commander'
9
+ require 'commander/import'
10
+ require 'mysqlaudit'
11
+ require 'mysqlaudit/actions'
12
+ require 'mysqlaudit/version'
13
+
14
+ program :name, 'MySQL Audit Tool'
15
+ program :version, Mysqlaudit::VERSION
16
+ program :description, 'MySQL tool for audit all tables with triggers.'
17
+ program :help, 'Author', 'Nicola Strappazzon <nicola51980@gmail.com>'
18
+
19
+ command :install do |c|
20
+ c.description = 'Install audit table'
21
+ c.syntax = 'audits install --host 127.0.0.1 --user root [--pass admin] --schema sakila [--table foo]'
22
+ c.option '--host STRING', String, 'Host'
23
+ c.option '--user STRING', String, 'User'
24
+ c.option '--pass STRING', String, 'Password'
25
+ c.option '--schema STRING', String, 'Schema name'
26
+ c.option '--table STRING', String, 'Table name'
27
+ c.action do |args, options|
28
+ Mysqlaudit::Actions.new(options.host, options.user, options.pass, options.schema, options.table).install
29
+ end
30
+ end
31
+
32
+ command :uninstall do |c|
33
+ c.description = 'Uninstall audit table'
34
+ c.syntax = 'audits uninstall --host 127.0.0.1 --user root [--pass admin] --schema sakila [--table foo] [--[no-]drop-audit-table]'
35
+ c.option '--host STRING', String, 'Host'
36
+ c.option '--user STRING', String, 'User'
37
+ c.option '--pass STRING', String, 'Password'
38
+ c.option '--schema STRING', String, 'Schema name'
39
+ c.option '--table STRING', String, 'Table name'
40
+ c.option '--[no-]drop-audit-table', 'Drop audit table'
41
+ c.action do |args, options|
42
+ Mysqlaudit::Actions.new(options.host, options.user, options.pass, options.schema, options.table, options.drop_audit_table).uninstall
43
+ end
44
+ end
45
+
46
+ command :rollback do |c|
47
+ c.description = 'Rollback transaction cath in audit table'
48
+ c.syntax = 'audits rollback --host 127.0.0.1 --user root [--pass admin] --schema sakila --table foo --statement INSERT'
49
+ c.option '--host STRING', String, 'Host'
50
+ c.option '--user STRING', String, 'User'
51
+ c.option '--pass STRING', String, 'Password'
52
+ c.option '--schema STRING', String, 'Schema name'
53
+ c.option '--table STRING', String, 'Table name'
54
+ c.option '--statement STRING', String, 'Statement operation, INSERT, UPDATE, or DELETE'
55
+ c.action do |args, options|
56
+ Mysqlaudit::Actions.new(options.host, options.user, options.pass, options.schema, options.table, nil, options.statement).rollback
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ require 'mysqlaudit/audit'
2
+
3
+ module Mysqlaudit
4
+ class Actions
5
+ @audit = nil
6
+
7
+ def initialize(host, user, password, schema, table, drop = nil, statement = nil)
8
+ # def initialize(schema, table, options = {})
9
+ # options = {host: 'localhost', user: 'root', password: ''}.merge(options)
10
+
11
+ $host = host
12
+ $user = user
13
+ $password = password
14
+ $schema = schema
15
+ $table = table
16
+ $drop = drop
17
+ $statement = statement
18
+
19
+ @audit = Mysqlaudit::Audit.new($host, $user, $password, $schema)
20
+ end
21
+
22
+ def install()
23
+ @audit.create_table()
24
+
25
+ if !$table
26
+ tables = @audit.get_tables()
27
+ tables.each do | table |
28
+ @audit.create_trigger(table, :insert)
29
+ @audit.create_trigger(table, :update)
30
+ @audit.create_trigger(table, :delete)
31
+ end
32
+ else
33
+ @audit.create_trigger($table, :insert)
34
+ @audit.create_trigger($table, :update)
35
+ @audit.create_trigger($table, :delete)
36
+ end
37
+ end
38
+
39
+ def uninstall()
40
+ if !$table
41
+ tables = @audit.get_tables()
42
+ tables.each do | table |
43
+ @audit.drop_trigger(table, :insert)
44
+ @audit.drop_trigger(table, :update)
45
+ @audit.drop_trigger(table, :delete)
46
+ end
47
+ else
48
+ @audit.drop_trigger($table, :insert)
49
+ @audit.drop_trigger($table, :update)
50
+ @audit.drop_trigger($table, :delete)
51
+ end
52
+
53
+ if $drop
54
+ @audit.drop_table()
55
+ end
56
+ end
57
+
58
+ def rollback()
59
+ @audit.rollback($table, $statement.to_sym)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,168 @@
1
+ require 'rubygems'
2
+ require 'mysql2'
3
+
4
+ # @todo Optimize passing params on methods with maps.
5
+ # @todo Optimize is set params to execute methods.
6
+ # @todo Passing as parameter the tables names to apply triggers.
7
+ # @todo Change name for audit table.
8
+ # @todo If exist trigger then append to existing.
9
+ # @todo Backup if exist triggers.
10
+ # @todo Define expiration time for events.
11
+
12
+ module Mysqlaudit
13
+ class Audit
14
+ def initialize(host, user, password, schema)
15
+ $host = host
16
+ $user = user
17
+ $password = password
18
+ $schema = schema
19
+
20
+ connect
21
+ end
22
+
23
+ def connect()
24
+ begin
25
+ @mysql = Mysql2::Client.new(:host => $host,
26
+ :username => $user,
27
+ :password => $password,
28
+ :database => $schema)
29
+ rescue
30
+ puts "Can't connect to MySQL Server."
31
+ exit 1
32
+ end
33
+ end
34
+
35
+ def create_table()
36
+ if !has_table
37
+ sql = <<SQL
38
+ CREATE TABLE IF NOT EXISTS audits (
39
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
40
+ type enum('I', 'U', 'D') NOT NULL,
41
+ `table` char(64) NOT NULL,
42
+ `column` char(64) NOT NULL,
43
+ `primary_key` int(10) unsigned,
44
+ `old` TEXT DEFAULT NULL,
45
+ `new` TEXT DEFAULT NULL,
46
+ `trigger_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
47
+ PRIMARY KEY (id),
48
+ UNIQUE KEY id_UNIQUE (id)
49
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
50
+ SQL
51
+
52
+ @mysql.query(sql)
53
+ puts "Created table audits in #{$schema} database."
54
+ end
55
+ end
56
+
57
+ def has_table()
58
+ sql = "SELECT true AS has FROM information_schema.tables WHERE table_schema = '#{$schema}' AND table_name = 'audits';"
59
+ @mysql.query(sql).count == 1
60
+ end
61
+
62
+ def drop_table()
63
+ return unless has_table()
64
+ @mysql.query("DROP TABLE IF EXISTS audits;")
65
+ puts "Delete table audits in #{$schema} database."
66
+ end
67
+
68
+ def get_tables()
69
+ sql = "SELECT table_name AS name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '#{$schema}' AND table_name <> 'audits';"
70
+ sql_result = @mysql.query(sql)
71
+ sql_result.map { |table| table['name']}
72
+ end
73
+
74
+ def get_primary_key(table)
75
+ sql = "SELECT column_name AS name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='#{$schema}' AND TABLE_NAME='#{table}' AND column_key = 'PRI';"
76
+ @mysql.query(sql).first['name']
77
+ end
78
+
79
+ def get_columns(table)
80
+ sql = "SELECT column_name AS name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='#{$schema}' AND TABLE_NAME='#{table}' AND column_key <> 'PRI';"
81
+ sql_result = @mysql.query(sql)
82
+ sql_result.map { |table| table['name']}
83
+ end
84
+
85
+ def create_trigger(table, actionName)
86
+ if !has_trigger(table, actionName)
87
+ columns = get_columns(table)
88
+ key = get_primary_key(table)
89
+
90
+ if key
91
+ sql = "CREATE TRIGGER audit_#{table}_#{actionName.downcase} AFTER #{actionName.upcase} ON #{table}\nFOR EACH ROW BEGIN\n"
92
+
93
+ columns.each do | column |
94
+ case actionName.downcase
95
+ when :insert
96
+ sql << "INSERT INTO audits (type, `table`, `column`, primary_key, old, new) VALUES ('#{actionName[0,1].upcase}', '#{table}', '#{column}', NEW.#{key}, NULL, NEW.#{column});\n"
97
+ when :update
98
+ sql << "IF OLD.#{column} <> NEW.#{column} THEN\n"
99
+ sql << " INSERT INTO audits (type, `table`, `column`, primary_key, old, new) VALUES ('#{actionName[0,1].upcase}', '#{table}', '#{column}', NEW.#{key}, OLD.#{column}, NEW.#{column});\n"
100
+ sql << "END IF;\n"
101
+ when :delete
102
+ sql << "INSERT INTO audits (type, `table`, `column`, primary_key, old, new) VALUES ('#{actionName[0,1].upcase}', '#{table}', '#{column}', OLD.#{key}, OLD.#{column}, NULL);\n"
103
+ end
104
+ end
105
+
106
+ sql << "END;"
107
+
108
+ @mysql.query(sql)
109
+ puts "Created trigger in #{table} table after #{actionName}."
110
+ else
111
+ puts "Impossible to create trigger in #{table}, not have primary key."
112
+ end
113
+ else
114
+ puts "Impossible to create trigger in #{table}, has a trigger."
115
+ end
116
+ end
117
+
118
+ def drop_trigger(table, actionName)
119
+ if has_trigger(table, actionName)
120
+ sql = "DROP TRIGGER IF EXISTS audit_#{table}_#{actionName};"
121
+ @mysql.query(sql)
122
+ puts "Deleted trigger in #{table} table after #{actionName}."
123
+ end
124
+ end
125
+
126
+ def has_trigger(table, actionName)
127
+ sql = "SELECT true AS has FROM information_schema.triggers WHERE trigger_schema = '#{$schema}' AND trigger_name = 'audit_#{table}_#{actionName}';"
128
+ @mysql.query(sql).count == 1
129
+ end
130
+
131
+ def build_pivot_query(table, statement)
132
+ columns = get_columns($table)
133
+ key = get_primary_key($table)
134
+
135
+ case statement
136
+ when :insert
137
+ sql = <<SQL
138
+ SELECT CONCAT('/* ', trigger_at, ' */ DELETE FROM #{$table} WHERE id = ', MAX(primary_key),';') AS `row`
139
+ FROM audits
140
+ WHERE `type` = 'I'
141
+ AND `table` = '#{$table}'
142
+ GROUP BY primary_key
143
+ ORDER BY trigger_at, primary_key, `column` ASC;
144
+ SQL
145
+ when :delete
146
+ sql = "SELECT CONCAT('/* ', trigger_at, ' */ INSERT INTO #{$table} (#{key}, #{columns.join(', ')}) VALUES (', primary_key, ', ', "
147
+ columns.each do | column |
148
+ sql << " QUOTE(MAX(IF(`column` = '#{column}', `old`, NULL))), ', ', "
149
+ end
150
+ sql = sql.chomp(" ', ', ")
151
+ sql << "');'"
152
+ sql << ") AS `row` FROM audits WHERE `type` = 'D' AND `table` = '#{$table}' GROUP BY primary_key ORDER BY trigger_at, primary_key, `column` ASC;"
153
+ when :update
154
+ sql = "SELECT CONCAT('/* ', trigger_at, ' */ UPDATE users SET ', `column`, ' = ', QUOTE(old), ' WHERE id = ', primary_key, ';') AS `row`"
155
+ sql << "FROM audits WHERE `type` = 'U' AND `table` = '#{$table}' ORDER BY trigger_at, primary_key, `column` ASC;"
156
+ end
157
+
158
+ sql_result = @mysql.query(sql)
159
+ sql_result.each(:as => :array) do |row|
160
+ puts row
161
+ end
162
+ end
163
+
164
+ def rollback(table, statement)
165
+ build_pivot_query(table, statement)
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,3 @@
1
+ module Mysqlaudit
2
+ VERSION = '0.0.1'
3
+ end
data/lib/mysqlaudit.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'mysqlaudit/actions'
3
+ require 'mysqlaudit/audit'
4
+ require 'mysqlaudit/version'
5
+
6
+ module Mysqlaudit
7
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysqlaudit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nicola Strappazzon C.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: MySQL tool for audit all tables with triggers.
14
+ email: nicola51980@gmail.com
15
+ executables:
16
+ - mysqlaudit
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/mysqlaudit
21
+ - lib/mysqlaudit/actions.rb
22
+ - lib/mysqlaudit/audit.rb
23
+ - lib/mysqlaudit/version.rb
24
+ - lib/mysqlaudit.rb
25
+ - LICENSE.txt
26
+ - README.md
27
+ homepage: https://github.com/nicola51980/mysqlaudit
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.0.5
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: MySQL Audit Tool
51
+ test_files: []