posgra 0.1.0

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.
@@ -0,0 +1,354 @@
1
+ class Posgra::Driver
2
+ include Posgra::Logger::Helper
3
+ include Posgra::Utils::Helper
4
+
5
+ DEFAULT_ACL = '{%s=arwdDxt/%s}'
6
+
7
+ PRIVILEGE_TYPES = {
8
+ 'a' => 'INSERT',
9
+ 'r' => 'SELECT',
10
+ 'w' => 'UPDATE',
11
+ 'd' => 'DELETE',
12
+ 'D' => 'TRUNCATE',
13
+ 'x' => 'REFERENCES',
14
+ 't' => 'TRIGGER',
15
+ }
16
+
17
+ def initialize(client, options = {})
18
+ unless client.type_map_for_results.is_a?(PG::TypeMapAllStrings)
19
+ raise 'PG::Connection#type_map_for_results must be PG::TypeMapAllStrings'
20
+ end
21
+
22
+ @client = client
23
+ @options = options
24
+ @identifier = options.fetch(:identifier)
25
+ end
26
+
27
+ def create_user(user)
28
+ updated = false
29
+
30
+ password = @identifier.identify(user)
31
+ sql = "CREATE USER #{@client.escape_identifier(user)} PASSWORD #{@client.escape_literal(password)}"
32
+ log(:info, sql, :color => :cyan)
33
+
34
+ unless @options[:dry_run]
35
+ @client.query(sql)
36
+ updated = true
37
+ end
38
+
39
+ updated
40
+ end
41
+
42
+ def drop_user(user)
43
+ updated = false
44
+
45
+ sql = "DROP USER #{@client.escape_identifier(user)}"
46
+ log(:info, sql, :color => :red)
47
+
48
+ unless @options[:dry_run]
49
+ @client.query(sql)
50
+ updated = true
51
+ end
52
+
53
+ updated
54
+ end
55
+
56
+ def create_group(group)
57
+ updated = false
58
+
59
+ sql = "CREATE GROUP #{@client.escape_identifier(group)}"
60
+ log(:info, sql, :color => :cyan)
61
+
62
+ unless @options[:dry_run]
63
+ @client.query(sql)
64
+ updated = true
65
+ end
66
+
67
+ updated
68
+ end
69
+
70
+ def add_user_to_group(user, group)
71
+ updated = false
72
+
73
+ sql = "ALTER GROUP #{@client.escape_identifier(group)} ADD USER #{@client.escape_identifier(user)}"
74
+ log(:info, sql, :color => :green)
75
+
76
+ unless @options[:dry_run]
77
+ @client.query(sql)
78
+ updated = true
79
+ end
80
+
81
+ updated
82
+ end
83
+
84
+ def drop_user_from_group(user, group)
85
+ updated = false
86
+
87
+ sql = "ALTER GROUP #{@client.escape_identifier(group)} DROP USER #{@client.escape_identifier(user)}"
88
+ log(:info, sql, :color => :cyan)
89
+
90
+ unless @options[:dry_run]
91
+ @client.query(sql)
92
+ updated = true
93
+ end
94
+
95
+ updated
96
+ end
97
+
98
+ def drop_group(group)
99
+ updated = false
100
+
101
+ sql = "DROP GROUP #{@client.escape_identifier(group)}"
102
+ log(:info, sql, :color => :red)
103
+
104
+ unless @options[:dry_run]
105
+ @client.query(sql)
106
+ updated = true
107
+ end
108
+
109
+ updated
110
+ end
111
+
112
+ def revoke_all_on_schema(role, schema)
113
+ updated = false
114
+
115
+ sql = "REVOKE ALL ON ALL TABLES IN SCHEMA #{@client.escape_identifier(schema)} FROM #{@client.escape_identifier(role)}"
116
+ log(:info, sql, :color => :green)
117
+
118
+ unless @options[:dry_run]
119
+ @client.query(sql)
120
+ updated = true
121
+ end
122
+
123
+ updated
124
+ end
125
+
126
+ def revoke_all_on_object(role, schema, object)
127
+ updated = false
128
+
129
+ sql = "REVOKE ALL ON #{@client.escape_identifier(schema)}.#{@client.escape_identifier(object)} FROM #{@client.escape_identifier(role)}"
130
+ log(:info, sql, :color => :green)
131
+
132
+ unless @options[:dry_run]
133
+ @client.query(sql)
134
+ updated = true
135
+ end
136
+
137
+ updated
138
+ end
139
+
140
+ def grant(role, priv, options, schema, object)
141
+ updated = false
142
+
143
+ sql = "GRANT #{priv} ON #{@client.escape_identifier(schema)}.#{@client.escape_identifier(object)} TO #{@client.escape_identifier(role)}"
144
+
145
+ if options['is_grantable']
146
+ sql << ' WITH GRANT OPTION'
147
+ end
148
+
149
+ log(:info, sql, :color => :green)
150
+
151
+ unless @options[:dry_run]
152
+ @client.query(sql)
153
+ updated = true
154
+ end
155
+
156
+ updated
157
+ end
158
+
159
+ def update_grant_options(role, priv, options, schema, object)
160
+ updated = false
161
+
162
+ if options.fetch('is_grantable')
163
+ updated = grant_grant_option(role, priv, schema, object)
164
+ else
165
+ updated = roveke_grant_option(role, priv, schema, object)
166
+ end
167
+
168
+ updated
169
+ end
170
+
171
+ def grant_grant_option(role, priv, schema, object)
172
+ updated = false
173
+
174
+ sql = "GRANT #{priv} ON #{@client.escape_identifier(schema)}.#{@client.escape_identifier(object)} TO #{@client.escape_identifier(role)} WITH GRANT OPTION"
175
+ log(:info, sql, :color => :green)
176
+
177
+ unless @options[:dry_run]
178
+ @client.query(sql)
179
+ updated = true
180
+ end
181
+
182
+ updated
183
+ end
184
+
185
+ def roveke_grant_option(role, priv, schema, object)
186
+ updated = false
187
+
188
+ sql = "REVOKE GRANT OPTION FOR #{priv} ON #{@client.escape_identifier(schema)}.#{@client.escape_identifier(object)} FROM #{@client.escape_identifier(role)}"
189
+ log(:info, sql, :color => :green)
190
+
191
+ unless @options[:dry_run]
192
+ @client.query(sql)
193
+ updated = true
194
+ end
195
+
196
+ updated
197
+ end
198
+
199
+ def revoke(role, priv, schema, object)
200
+ updated = false
201
+
202
+ sql = "REVOKE #{priv} ON #{@client.escape_identifier(schema)}.#{@client.escape_identifier(object)} FROM #{@client.escape_identifier(role)}"
203
+ log(:info, sql, :color => :green)
204
+
205
+ unless @options[:dry_run]
206
+ @client.query(sql)
207
+ updated = true
208
+ end
209
+
210
+ updated
211
+ end
212
+
213
+ def describe_objects(schema)
214
+ rs = @client.exec <<-SQL
215
+ SELECT
216
+ pg_class.relname,
217
+ pg_namespace.nspname
218
+ FROM
219
+ pg_class
220
+ INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
221
+ WHERE
222
+ pg_namespace.nspname = #{@client.escape_literal(schema)}
223
+ AND pg_class.relkind NOT IN ('i')
224
+ SQL
225
+
226
+ rs.map do |row|
227
+ row.fetch('relname')
228
+ end
229
+ end
230
+
231
+ def describe_users
232
+ rs = @client.exec('SELECT * FROM pg_user')
233
+
234
+ options_by_user = {}
235
+
236
+ rs.each do |row|
237
+ user = row.fetch('usename')
238
+ next unless matched?(user, @options[:include_role], @options[:exclude_role])
239
+ options_by_user[user] = row.select {|_, v| v == 't' }.keys
240
+ end
241
+
242
+ options_by_user
243
+ end
244
+
245
+ def describe_groups
246
+ rs = @client.exec <<-SQL
247
+ SELECT
248
+ pg_group.groname,
249
+ pg_user.usename
250
+ FROM
251
+ pg_group
252
+ LEFT JOIN pg_user ON pg_user.usesysid = ANY(pg_group.grolist)
253
+ SQL
254
+
255
+ users_by_group = {}
256
+
257
+ rs.each do |row|
258
+ group = row.fetch('groname')
259
+ user = row.fetch('usename')
260
+ next unless [group, user].any? {|i| matched?(i, @options[:include_role], @options[:exclude_role]) }
261
+ users_by_group[group] ||= []
262
+ users_by_group[group] << user if user
263
+ end
264
+
265
+ users_by_group
266
+ end
267
+
268
+ def describe_grants
269
+ rs = @client.exec <<-SQL
270
+ SELECT
271
+ pg_class.relname,
272
+ pg_namespace.nspname,
273
+ pg_class.relacl,
274
+ pg_user.usename
275
+ FROM
276
+ pg_class
277
+ INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
278
+ INNER JOIN pg_user ON pg_class.relowner = pg_user.usesysid
279
+ WHERE
280
+ pg_class.relkind NOT IN ('i')
281
+ SQL
282
+
283
+ grants_by_role = {}
284
+ rs.each do |row|
285
+ relname = row.fetch('relname')
286
+ nspname = row.fetch('nspname')
287
+ relacl = row.fetch('relacl')
288
+ usename = row.fetch('usename')
289
+
290
+ next unless matched?(nspname, @options[:include_schema], @options[:exclude_schema])
291
+
292
+ parse_aclitems(relacl, usename).each do |aclitem|
293
+ role = aclitem.fetch('grantee')
294
+ privs = aclitem.fetch('privileges')
295
+ next unless matched?(role, @options[:include_role], @options[:exclude_role])
296
+ grants_by_role[role] ||= {}
297
+ grants_by_role[role][nspname] ||= {}
298
+ grants_by_role[role][nspname][relname] = privs
299
+ end
300
+ end
301
+
302
+ grants_by_role
303
+ end
304
+
305
+ def describe_schemas
306
+ rs = @client.exec <<-SQL
307
+ SELECT
308
+ nspname
309
+ FROM
310
+ pg_namespace
311
+ SQL
312
+
313
+ rs.map do |row|
314
+ row.fetch('nspname')
315
+ end
316
+ end
317
+
318
+ private
319
+
320
+ def parse_aclitems(aclitems, owner)
321
+ aclitems ||= DEFAULT_ACL % [owner, owner]
322
+ aclitems = aclitems[1..-2].split(',')
323
+
324
+ aclitems.map do |aclitem|
325
+ grantee, privileges_grantor = aclitem.split('=', 2)
326
+ privileges, grantor = privileges_grantor.split('/', 2)
327
+
328
+ {
329
+ 'grantee' => grantee,
330
+ 'privileges' => expand_privileges(privileges),
331
+ 'grantor' => grantor,
332
+ }
333
+ end
334
+ end
335
+
336
+ def expand_privileges(privileges)
337
+ options_by_privilege = {}
338
+
339
+ privileges.scan(/([a-z])(\*)?/i).each do |privilege_type_char,is_grantable|
340
+ privilege_type = PRIVILEGE_TYPES[privilege_type_char]
341
+
342
+ unless privilege_type
343
+ log(:warn, "unknown privilege type: #{privilege_type_char}", :color => :yellow)
344
+ next
345
+ end
346
+
347
+ options_by_privilege[privilege_type] = {
348
+ 'is_grantable' => !!is_grantable,
349
+ }
350
+ end
351
+
352
+ options_by_privilege
353
+ end
354
+ end
@@ -0,0 +1,17 @@
1
+ class Posgra::DSL
2
+ def self.convert_roles(exported, options = {})
3
+ Posgra::DSL::Converter.convert_roles(exported, options)
4
+ end
5
+
6
+ def self.convert_grants(exported, options = {})
7
+ Posgra::DSL::Converter.convert_grants(exported, options)
8
+ end
9
+
10
+ def self.parse_roles(dsl, path, options = {})
11
+ Posgra::DSL::Roles.eval(dsl, path, options).result
12
+ end
13
+
14
+ def self.parse_grants(dsl, path, options = {})
15
+ Posgra::DSL::Grants.eval(dsl, path, options).result
16
+ end
17
+ end
@@ -0,0 +1,136 @@
1
+ class Posgra::DSL::Converter
2
+ def self.convert_roles(exported, options = {})
3
+ self.new(exported, options).convert_roles
4
+ end
5
+
6
+ def self.convert_grants(exported, options = {})
7
+ self.new(exported, options).convert_grants
8
+ end
9
+
10
+ def initialize(exported, options = {})
11
+ @exported = exported
12
+ @options = options
13
+ end
14
+
15
+ def convert_roles
16
+ users_by_group = @exported[:users_by_group] || {}
17
+ users = @exported.fetch(:users, []) - users_by_group.values.flatten
18
+
19
+ [
20
+ output_users(users),
21
+ output_groups(users_by_group),
22
+ ].join("\n").strip
23
+ end
24
+
25
+ def convert_grants
26
+ grants_by_role = @exported || {}
27
+ output_roles(grants_by_role).strip
28
+ end
29
+
30
+ private
31
+
32
+ def output_users(users)
33
+ users.sort.map {|user|
34
+ "user #{user.inspect}"
35
+ }.join("\n") + "\n"
36
+ end
37
+
38
+ def output_groups(users_by_group)
39
+ users_by_group.sort_by {|g, _| g }.map {|group, users|
40
+ output_group(group, users)
41
+ }.join("\n")
42
+ end
43
+
44
+ def output_group(group, users)
45
+ if users.empty?
46
+ users = "# no users"
47
+ else
48
+ users = users.sort.map {|user|
49
+ "user #{user.inspect}"
50
+ }.join("\n ")
51
+ end
52
+
53
+ <<-EOS
54
+ group #{group.inspect} do
55
+ #{users}
56
+ end
57
+ EOS
58
+ end
59
+
60
+ def output_roles(grants_by_role)
61
+ grants_by_role.sort_by {|r, _| r }.map {|role, grants_by_schema|
62
+ output_role(role, grants_by_schema)
63
+ }.join("\n")
64
+ end
65
+
66
+ def output_role(role, grants_by_schema)
67
+ if grants_by_schema.empty?
68
+ schemas = "# no schemas"
69
+ else
70
+ schemas = output_schemas(grants_by_schema)
71
+ end
72
+
73
+ <<-EOS
74
+ role #{role.inspect} do
75
+ #{schemas}
76
+ end
77
+ EOS
78
+ end
79
+
80
+ def output_schemas(grants_by_schema)
81
+ grants_by_schema.sort_by {|s, _| s }.map {|schema, grants_by_object|
82
+ output_schema(schema, grants_by_object).strip
83
+ }.join("\n ")
84
+ end
85
+
86
+ def output_schema(schema, grants_by_object)
87
+ if grants_by_object.empty?
88
+ objects = "# no objects"
89
+ else
90
+ objects = output_objects(grants_by_object)
91
+ end
92
+
93
+ <<-EOS
94
+ schema #{schema.inspect} do
95
+ #{objects}
96
+ end
97
+ EOS
98
+ end
99
+
100
+ def output_objects(grants_by_object)
101
+ grants_by_object.sort_by {|o, _| o }.map {|object, grants|
102
+ output_object(object, grants).strip
103
+ }.join("\n ")
104
+ end
105
+
106
+ def output_object(object, grants)
107
+ if grants.empty?
108
+ grants = "# no grants"
109
+ else
110
+ grants = output_grants(grants)
111
+ end
112
+
113
+ <<-EOS
114
+ on #{object.inspect} do
115
+ #{grants}
116
+ end
117
+ EOS
118
+ end
119
+
120
+ def output_grants(grants)
121
+ grants.sort_by {|g| g.to_s }.map {|privilege_type, options|
122
+ output_grant(privilege_type, options).strip
123
+ }.join("\n ")
124
+ end
125
+
126
+ def output_grant(privilege_type, options)
127
+ is_grantable = options.fetch('is_grantable')
128
+ out = "grant #{privilege_type.inspect}"
129
+
130
+ if is_grantable
131
+ out << ", :grantable => #{is_grantable}"
132
+ end
133
+
134
+ out
135
+ end
136
+ end