posgra 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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