gratan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +86 -0
- data/Rakefile +5 -0
- data/bin/gratan +132 -0
- data/gratan.gemspec +28 -0
- data/lib/gratan/client.rb +211 -0
- data/lib/gratan/driver.rb +166 -0
- data/lib/gratan/dsl/context/on.rb +19 -0
- data/lib/gratan/dsl/context/user.rb +25 -0
- data/lib/gratan/dsl/context.rb +57 -0
- data/lib/gratan/dsl/converter.rb +74 -0
- data/lib/gratan/dsl/validator.rb +13 -0
- data/lib/gratan/dsl.rb +9 -0
- data/lib/gratan/exporter.rb +49 -0
- data/lib/gratan/ext/string_ext.rb +25 -0
- data/lib/gratan/grant_parser.rb +68 -0
- data/lib/gratan/identifier/auto.rb +28 -0
- data/lib/gratan/identifier/csv.rb +25 -0
- data/lib/gratan/identifier/null.rb +5 -0
- data/lib/gratan/identifier.rb +2 -0
- data/lib/gratan/logger.rb +28 -0
- data/lib/gratan/version.rb +3 -0
- data/lib/gratan.rb +24 -0
- data/spec/change/change_grants_2_spec.rb +154 -0
- data/spec/change/change_grants_3_spec.rb +164 -0
- data/spec/change/change_grants_4_spec.rb +37 -0
- data/spec/change/change_grants_spec.rb +209 -0
- data/spec/create/create_user_2_spec.rb +139 -0
- data/spec/create/create_user_3_spec.rb +115 -0
- data/spec/create/create_user_spec.rb +194 -0
- data/spec/drop/drop_user_2_spec.rb +77 -0
- data/spec/drop/drop_user_spec.rb +67 -0
- data/spec/drop/expire_user_spec.rb +179 -0
- data/spec/export/export_spec.rb +119 -0
- data/spec/misc/misc_spec.rb +74 -0
- data/spec/misc/require_spec.rb +77 -0
- data/spec/spec_helper.rb +118 -0
- metadata +198 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
class Gratan::DSL::Context::On
|
2
|
+
include Gratan::DSL::Validator
|
3
|
+
|
4
|
+
attr_reader :result
|
5
|
+
|
6
|
+
def initialize(user, host, object, &block)
|
7
|
+
@error_identifier = "User `#{user}@#{host}` on `#{object}`"
|
8
|
+
@result = []
|
9
|
+
instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def grant(name, options = {})
|
13
|
+
__validate("Grant `#{name}` is already defined") do
|
14
|
+
not @result.include?(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
@result << name
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Gratan::DSL::Context::User
|
2
|
+
include Gratan::DSL::Validator
|
3
|
+
|
4
|
+
attr_reader :result
|
5
|
+
|
6
|
+
def initialize(user, host, &block)
|
7
|
+
@error_identifier = "User `#{user}@#{host}`"
|
8
|
+
@user = user
|
9
|
+
@host = host
|
10
|
+
@result = {}
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on(name, options = {}, &block)
|
15
|
+
name = name.to_s
|
16
|
+
|
17
|
+
__validate("Object `#{name}` is already defined") do
|
18
|
+
not @result.has_key?(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
grant = {:privs => Gratan::DSL::Context::On.new(@user, @host, name, &block).result}
|
22
|
+
grant[:with] = options[:with] if options[:with]
|
23
|
+
@result[name] = grant
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Gratan::DSL::Context
|
2
|
+
include Gratan::DSL::Validator
|
3
|
+
include Gratan::Logger::Helper
|
4
|
+
|
5
|
+
def self.eval(dsl, path, options = {})
|
6
|
+
self.new(path, options) do
|
7
|
+
eval(dsl, binding, path)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :result
|
12
|
+
|
13
|
+
def initialize(path, options = {}, &block)
|
14
|
+
@path = path
|
15
|
+
@options = options
|
16
|
+
@result = {}
|
17
|
+
instance_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def require(file)
|
23
|
+
grantfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file))
|
24
|
+
|
25
|
+
if File.exist?(grantfile)
|
26
|
+
instance_eval(File.read(grantfile), grantfile)
|
27
|
+
elsif File.exist?(grantfile + '.rb')
|
28
|
+
instance_eval(File.read(grantfile + '.rb'), grantfile + '.rb')
|
29
|
+
else
|
30
|
+
Kernel.require(file)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def user(name, host, options = {}, &block)
|
35
|
+
name = name.to_s
|
36
|
+
host = host.to_s
|
37
|
+
options ||= {}
|
38
|
+
|
39
|
+
__validate("User `#{name}@#{host}` is already defined") do
|
40
|
+
not @result.has_key?([name, host])
|
41
|
+
end
|
42
|
+
|
43
|
+
if @options[:enable_expired] and (expired = options.delete(:expired))
|
44
|
+
expired = Time.parse(expired)
|
45
|
+
|
46
|
+
if Time.new >= expired
|
47
|
+
log(:warn, "User `#{name}@#{host}` has expired", :yellow)
|
48
|
+
return
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@result[[name, host]] = {
|
53
|
+
:objects => Gratan::DSL::Context::User.new(name, host, &block).result,
|
54
|
+
:options => options,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Gratan::DSL::Converter
|
2
|
+
def self.convert(exported, options = {})
|
3
|
+
self.new(exported, options).convert
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(exported, options = {})
|
7
|
+
@exported = exported
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def convert
|
12
|
+
@exported.map {|user_host, attrs|
|
13
|
+
output_user(user_host, attrs)
|
14
|
+
}.join("\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def output_user(user_host, attrs)
|
20
|
+
user, host = user_host
|
21
|
+
objects, options = attrs.values_at(:objects, :options)
|
22
|
+
options = output_user_options(options)
|
23
|
+
|
24
|
+
<<-EOS
|
25
|
+
user #{user.inspect}, #{host.inspect}#{options}do
|
26
|
+
#{output_objects(objects)}
|
27
|
+
end
|
28
|
+
EOS
|
29
|
+
end
|
30
|
+
|
31
|
+
def output_user_options(options)
|
32
|
+
if options.empty?
|
33
|
+
' '
|
34
|
+
else
|
35
|
+
options = strip_hash_brace(options.inspect)
|
36
|
+
", #{options} "
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_objects(objects)
|
41
|
+
objects.map {|object, grant|
|
42
|
+
options = output_object_options(grant)
|
43
|
+
|
44
|
+
<<-EOS
|
45
|
+
on #{object.inspect}#{options}do
|
46
|
+
#{output_grant(grant)}
|
47
|
+
end
|
48
|
+
EOS
|
49
|
+
}.join("\n").strip
|
50
|
+
end
|
51
|
+
|
52
|
+
def output_object_options(grant)
|
53
|
+
with_option = grant.delete(:with)
|
54
|
+
|
55
|
+
if with_option
|
56
|
+
options = strip_hash_brace({:with => with_option}.inspect)
|
57
|
+
", #{options} "
|
58
|
+
else
|
59
|
+
' '
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def output_grant(grant)
|
64
|
+
grant[:privs].map {|priv|
|
65
|
+
<<-EOS
|
66
|
+
grant #{priv.inspect}
|
67
|
+
EOS
|
68
|
+
}.join.strip
|
69
|
+
end
|
70
|
+
|
71
|
+
def strip_hash_brace(hash_str)
|
72
|
+
hash_str.sub(/\A\{/, '').sub(/\}\z/, '')
|
73
|
+
end
|
74
|
+
end
|
data/lib/gratan/dsl.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
class Gratan::Exporter
|
2
|
+
def self.export(driver, options = {}, &block)
|
3
|
+
self.new(driver, options).export(&block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(driver, options = {})
|
7
|
+
@driver = driver
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def export
|
12
|
+
grants = []
|
13
|
+
|
14
|
+
@driver.each_user do |user, host|
|
15
|
+
next if user =~ @options[:ignore_user]
|
16
|
+
|
17
|
+
@driver.show_grants(user, host) do |stmt|
|
18
|
+
grants << Gratan::GrantParser.parse(stmt)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
pack(grants)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def pack(grants)
|
28
|
+
packed = {}
|
29
|
+
|
30
|
+
grants.each do |grant|
|
31
|
+
user = grant.delete(:user)
|
32
|
+
host = grant.delete(:host)
|
33
|
+
user_host = [user, host]
|
34
|
+
object = grant.delete(:object)
|
35
|
+
identified = grant.delete(:identified)
|
36
|
+
required = grant.delete(:require)
|
37
|
+
|
38
|
+
packed[user_host] ||= {:objects => {}, :options => {}}
|
39
|
+
packed[user_host][:objects][object] = grant
|
40
|
+
packed[user_host][:options][:required] = required if required
|
41
|
+
|
42
|
+
if @options[:with_identifier] and identified
|
43
|
+
packed[user_host][:options][:identified] = identified
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
packed
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class String
|
2
|
+
@@colorize = false
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def colorize=(value)
|
6
|
+
@@colorize = value
|
7
|
+
end
|
8
|
+
|
9
|
+
def colorize
|
10
|
+
@@colorize
|
11
|
+
end
|
12
|
+
end # of class methods
|
13
|
+
|
14
|
+
Term::ANSIColor::Attribute.named_attributes.map do |attribute|
|
15
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
16
|
+
def #{attribute.name}
|
17
|
+
if @@colorize
|
18
|
+
Term::ANSIColor.send(#{attribute.name.inspect}, self)
|
19
|
+
else
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
EOS
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Gratan::GrantParser
|
2
|
+
def initialize(stmt)
|
3
|
+
@stmt = stmt.strip
|
4
|
+
@parsed = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.parse(stmt)
|
8
|
+
parser = self.new(stmt)
|
9
|
+
parser.parse!
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse!
|
13
|
+
parse_grant
|
14
|
+
parse_require
|
15
|
+
parse_identified
|
16
|
+
parse_main
|
17
|
+
@parsed
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_grant
|
23
|
+
@stmt.slice!(/\s+WITH\s+(.+?)\z/)
|
24
|
+
with_option = $1
|
25
|
+
|
26
|
+
if with_option
|
27
|
+
@parsed[:with] = with_option.strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_require
|
32
|
+
@stmt.slice!(/\s+REQUIRE\s+(.+?)\z/)
|
33
|
+
required = $1
|
34
|
+
|
35
|
+
if required
|
36
|
+
@parsed[:require] = required.strip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_identified
|
41
|
+
@stmt.slice!(/\s+IDENTIFIED BY\s+(.+?)\z/)
|
42
|
+
identified = $1
|
43
|
+
|
44
|
+
if identified
|
45
|
+
@parsed[:identified] = identified.strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_main
|
50
|
+
md = /\AGRANT\s+(.+?)\s+ON\s+(.+?)\s+TO\s+'(.*)'@'(.+)'\z/.match(@stmt)
|
51
|
+
privs, object, user, host = md.captures
|
52
|
+
@parsed[:privs] = parse_privs(privs.strip)
|
53
|
+
@parsed[:object] = object.gsub('`', '').strip
|
54
|
+
@parsed[:user] = user
|
55
|
+
@parsed[:host] = host
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_privs(privs)
|
59
|
+
privs << ','
|
60
|
+
priv_list = []
|
61
|
+
|
62
|
+
while priv = privs.slice!(/\A[^,(]+(?:\([^)]+\))?\s*,\s*/)
|
63
|
+
priv_list << priv.strip.sub(/,\z/, '').strip
|
64
|
+
end
|
65
|
+
|
66
|
+
priv_list
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Gratan::Identifier::Auto
|
2
|
+
def initialize(output, options = {})
|
3
|
+
@options = options
|
4
|
+
|
5
|
+
if output == '-'
|
6
|
+
@output = $stdout
|
7
|
+
else
|
8
|
+
@output = open(output, 'w')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def identify(user, host)
|
13
|
+
password = mkpasswd
|
14
|
+
puts_password(user, host, password)
|
15
|
+
password
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def mkpasswd(len = 8)
|
21
|
+
[*1..9, *'A'..'Z', *'a'..'z'].shuffle.slice(0, len).join
|
22
|
+
end
|
23
|
+
|
24
|
+
def puts_password(user, host, password)
|
25
|
+
@output.puts("#{user}@#{host},#{password}")
|
26
|
+
@output.flush
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
class Gratan::Identifier::CSV
|
4
|
+
include Gratan::Logger::Helper
|
5
|
+
|
6
|
+
def initialize(path, options = {})
|
7
|
+
@options = options
|
8
|
+
@passwords = {}
|
9
|
+
|
10
|
+
CSV.foreach(path) do |row|
|
11
|
+
@passwords[row[0]] = row[1]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def identify(user, host)
|
16
|
+
user_host = "#{user}@#{host}"
|
17
|
+
password = @passwords[user_host]
|
18
|
+
|
19
|
+
unless password
|
20
|
+
log(:warn, "password for `#{user_host}` can not be found", :yellow)
|
21
|
+
end
|
22
|
+
|
23
|
+
password
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Gratan::Logger < ::Logger
|
2
|
+
include Singleton
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super($stdout)
|
6
|
+
|
7
|
+
self.formatter = proc do |severity, datetime, progname, msg|
|
8
|
+
"#{msg}\n"
|
9
|
+
end
|
10
|
+
|
11
|
+
self.level = Logger::INFO
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_debug(value)
|
15
|
+
self.level = value ? Logger::DEBUG : Logger::INFO
|
16
|
+
end
|
17
|
+
|
18
|
+
module Helper
|
19
|
+
def log(level, message, color = nil)
|
20
|
+
options = @options || {}
|
21
|
+
message = "[#{level.to_s.upcase}] #{message}" unless level == :info
|
22
|
+
message << ' (dry-run)' if options[:dry_run]
|
23
|
+
message = message.send(color) if color
|
24
|
+
logger = options[:logger] || Gratan::Logger.instance
|
25
|
+
logger.send(level, message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/gratan.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'mysql2'
|
3
|
+
require 'singleton'
|
4
|
+
require 'term/ansicolor'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Gratan; end
|
8
|
+
require 'gratan/logger'
|
9
|
+
require 'gratan/client'
|
10
|
+
require 'gratan/driver'
|
11
|
+
require 'gratan/dsl'
|
12
|
+
require 'gratan/dsl/validator'
|
13
|
+
require 'gratan/dsl/context'
|
14
|
+
require 'gratan/dsl/context/user'
|
15
|
+
require 'gratan/dsl/context/on'
|
16
|
+
require 'gratan/dsl/converter'
|
17
|
+
require 'gratan/exporter'
|
18
|
+
require 'gratan/ext/string_ext'
|
19
|
+
require 'gratan/grant_parser'
|
20
|
+
require 'gratan/identifier'
|
21
|
+
require 'gratan/identifier/auto'
|
22
|
+
require 'gratan/identifier/csv'
|
23
|
+
require 'gratan/identifier/null'
|
24
|
+
require 'gratan/version'
|
@@ -0,0 +1,154 @@
|
|
1
|
+
describe 'Gratan::Client#apply' do
|
2
|
+
before(:each) do
|
3
|
+
apply {
|
4
|
+
<<-RUBY
|
5
|
+
user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do
|
6
|
+
on '*.*' do
|
7
|
+
grant 'USAGE'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
user 'bob', 'localhost' do
|
12
|
+
on '*.*', with: 'GRANT OPTION' do
|
13
|
+
grant 'ALL PRIVILEGES'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when update password' do
|
21
|
+
subject { client }
|
22
|
+
|
23
|
+
it do
|
24
|
+
apply(subject) {
|
25
|
+
<<-RUBY
|
26
|
+
user 'scott', 'localhost', identified: '123', required: 'SSL' do
|
27
|
+
on '*.*' do
|
28
|
+
grant 'USAGE'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
user 'bob', 'localhost', identified: '456' do
|
33
|
+
on '*.*', with: 'GRANT OPTION' do
|
34
|
+
grant 'ALL PRIVILEGES'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
RUBY
|
38
|
+
}
|
39
|
+
|
40
|
+
expect(show_grants).to match_array [
|
41
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' IDENTIFIED BY PASSWORD '*531E182E2F72080AB0740FE2F2D689DBE0146E04' WITH GRANT OPTION",
|
42
|
+
"GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' REQUIRE SSL",
|
43
|
+
]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when remove password' do
|
48
|
+
subject { client }
|
49
|
+
|
50
|
+
it do
|
51
|
+
apply(subject) {
|
52
|
+
<<-RUBY
|
53
|
+
user 'scott', 'localhost', identified: nil, required: 'SSL' do
|
54
|
+
on '*.*' do
|
55
|
+
grant 'USAGE'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
user 'bob', 'localhost' do
|
60
|
+
on '*.*', with: 'GRANT OPTION' do
|
61
|
+
grant 'ALL PRIVILEGES'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
RUBY
|
65
|
+
}
|
66
|
+
|
67
|
+
expect(show_grants).to match_array [
|
68
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' WITH GRANT OPTION",
|
69
|
+
"GRANT USAGE ON *.* TO 'scott'@'localhost' REQUIRE SSL",
|
70
|
+
]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when skip update password' do
|
75
|
+
subject { client }
|
76
|
+
|
77
|
+
it do
|
78
|
+
apply(subject) {
|
79
|
+
<<-RUBY
|
80
|
+
user 'scott', 'localhost', required: 'SSL' do
|
81
|
+
on '*.*' do
|
82
|
+
grant 'USAGE'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
user 'bob', 'localhost' do
|
87
|
+
on '*.*', with: 'GRANT OPTION' do
|
88
|
+
grant 'ALL PRIVILEGES'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
RUBY
|
92
|
+
}
|
93
|
+
|
94
|
+
expect(show_grants).to match_array [
|
95
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' WITH GRANT OPTION",
|
96
|
+
"GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL",
|
97
|
+
]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when update require' do
|
102
|
+
subject { client }
|
103
|
+
|
104
|
+
it do
|
105
|
+
apply(subject) {
|
106
|
+
<<-RUBY
|
107
|
+
user 'scott', 'localhost', required: 'X509' do
|
108
|
+
on '*.*' do
|
109
|
+
grant 'USAGE'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
user 'bob', 'localhost', required: 'SSL' do
|
114
|
+
on '*.*', with: 'GRANT OPTION' do
|
115
|
+
grant 'ALL PRIVILEGES'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
RUBY
|
119
|
+
}
|
120
|
+
|
121
|
+
expect(show_grants).to match_array [
|
122
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' REQUIRE SSL WITH GRANT OPTION",
|
123
|
+
"GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE X509",
|
124
|
+
]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'when update with option' do
|
129
|
+
subject { client }
|
130
|
+
|
131
|
+
it do
|
132
|
+
apply(subject) {
|
133
|
+
<<-RUBY
|
134
|
+
user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do
|
135
|
+
on '*.*', with: 'GRANT OPTION MAX_QUERIES_PER_HOUR 1 MAX_UPDATES_PER_HOUR 2 MAX_CONNECTIONS_PER_HOUR 3 MAX_USER_CONNECTIONS 4' do
|
136
|
+
grant 'USAGE'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
user 'bob', 'localhost' do
|
141
|
+
on '*.*' do
|
142
|
+
grant 'ALL PRIVILEGES'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
RUBY
|
146
|
+
}
|
147
|
+
|
148
|
+
expect(show_grants).to match_array [
|
149
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost'",
|
150
|
+
"GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL WITH GRANT OPTION MAX_QUERIES_PER_HOUR 1 MAX_UPDATES_PER_HOUR 2 MAX_CONNECTIONS_PER_HOUR 3 MAX_USER_CONNECTIONS 4",
|
151
|
+
]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|