pg-ldap-sync 0.5.1 → 0.5.2

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.
@@ -1,437 +1,443 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'net/ldap'
4
- require 'optparse'
5
- require 'yaml'
6
- require 'kwalify'
7
- require 'pg'
3
+ require "net/ldap"
4
+ require "optparse"
5
+ require "erb"
6
+ require "yaml"
7
+ require "json-schema"
8
+ require "pg"
8
9
  require "pg_ldap_sync/logger"
9
10
 
10
11
  module PgLdapSync
11
- class Application
12
- attr_accessor :config_fname
13
- attr_accessor :log
14
- attr_accessor :test
15
-
16
- def string_to_symbol(hash)
17
- if hash.kind_of?(Hash)
18
- return hash.inject({}) do |h, v|
19
- raise "expected String instead of #{h.inspect}" unless v[0].kind_of?(String)
20
- h[v[0].intern] = string_to_symbol(v[1])
21
- h
12
+ class Application
13
+ attr_accessor :config_fname
14
+ attr_accessor :log
15
+ attr_accessor :test
16
+
17
+ def string_to_symbol(hash)
18
+ if hash.is_a?(Hash)
19
+ hash.each_with_object({}) do |v, h|
20
+ raise "expected String instead of #{h.inspect}" unless v[0].is_a?(String)
21
+ h[v[0].intern] = string_to_symbol(v[1])
22
+ end
23
+ else
24
+ hash
22
25
  end
23
- else
24
- return hash
25
26
  end
26
- end
27
27
 
28
-
29
- def validate_config(config, schema, fname)
30
- schema = YAML.load_file(schema)
31
- validator = Kwalify::Validator.new(schema)
32
- errors = validator.validate(config)
33
- if errors && !errors.empty?
34
- errors.each do |err|
35
- log.fatal "error in #{fname}: [#{err.path}] #{err.message}"
28
+ def validate_config(config, schema, fname)
29
+ schema = YAML.load_file(schema)
30
+ errors = JSON::Validator.fully_validate(schema, config, validate_schema: true, insert_defaults: true)
31
+ if errors && !errors.empty?
32
+ errors.each do |err|
33
+ log.fatal "error in #{fname}: #{err}"
34
+ end
35
+ raise InvalidConfig, 78 # EX_CONFIG
36
36
  end
37
- raise InvalidConfig, 78 # EX_CONFIG
38
37
  end
39
- end
40
38
 
41
- def read_config_file(fname)
42
- raise "Config file #{fname.inspect} does not exist" unless File.exist?(fname)
43
- config = YAML.load(File.read(fname))
39
+ def read_config_file(fname)
40
+ raise "Config file #{fname.inspect} does not exist" unless File.exist?(fname)
41
+ config = YAML.load(ERB.new(File.read(fname)).result)
44
42
 
45
- schema_fname = File.join(File.dirname(__FILE__), '../../config/schema.yaml')
46
- validate_config(config, schema_fname, fname)
43
+ schema_fname = File.join(File.dirname(__FILE__), "../../config/schema.yaml")
44
+ validate_config(config, schema_fname, fname)
47
45
 
48
- @config = string_to_symbol(config)
49
- end
46
+ @config = string_to_symbol(config)
47
+ end
50
48
 
51
- LdapRole = Struct.new :name, :dn, :member_dns
49
+ LdapRole = Struct.new :name, :dn, :member_dns
52
50
 
53
- def search_ldap_users
54
- ldap_user_conf = @config[:ldap_users]
55
- name_attribute = ldap_user_conf[:name_attribute]
51
+ def search_ldap_users
52
+ ldap_user_conf = @config[:ldap_users]
53
+ name_attribute = ldap_user_conf[:name_attribute]
56
54
 
57
- users = []
58
- res = @ldap.search(
59
- base: ldap_user_conf[:base],
60
- filter: ldap_user_conf[:filter],
61
- attributes: [name_attribute, :dn]
62
- ) do |entry|
63
- name = entry[name_attribute].first
55
+ users = []
56
+ res = @ldap.search(
57
+ base: ldap_user_conf[:base],
58
+ filter: ldap_user_conf[:filter],
59
+ attributes: [name_attribute, :dn]
60
+ ) do |entry|
61
+ name = entry[name_attribute].first
64
62
 
65
- unless name
66
- log.warn "user attribute #{name_attribute.inspect} not defined for #{entry.dn}"
67
- next
68
- end
69
- log.info "found user-dn: #{entry.dn}"
70
-
71
- names = if ldap_user_conf[:bothcase_name]
72
- [name, name.downcase].uniq
73
- elsif ldap_user_conf[:lowercase_name]
74
- [name.downcase]
75
- else
76
- [name]
77
- end
63
+ unless name
64
+ log.warn "user attribute #{name_attribute.inspect} not defined for #{entry.dn}"
65
+ next
66
+ end
67
+ log.info "found user-dn: #{entry.dn}"
68
+
69
+ names = if ldap_user_conf[:bothcase_name]
70
+ [name, name.downcase].uniq
71
+ elsif ldap_user_conf[:lowercase_name]
72
+ [name.downcase]
73
+ else
74
+ [name]
75
+ end
78
76
 
79
- names.each do |n|
80
- users << LdapRole.new(n, entry.dn)
81
- end
82
- entry.each do |attribute, values|
83
- log.debug " #{attribute}:"
84
- values.each do |value|
85
- log.debug " --->#{value.inspect}"
77
+ names.each do |n|
78
+ users << LdapRole.new(n, entry.dn)
79
+ end
80
+ entry.each do |attribute, values|
81
+ log.debug " #{attribute}:"
82
+ values.each do |value|
83
+ log.debug " --->#{value.inspect}"
84
+ end
86
85
  end
87
86
  end
87
+ raise LdapError, "LDAP: #{@ldap.get_operation_result.message}" unless res
88
+ users
88
89
  end
89
- raise LdapError, "LDAP: #{@ldap.get_operation_result.message}" unless res
90
- return users
91
- end
92
90
 
93
- def retrieve_array_attribute(entry, attribute_name)
94
- array = entry[attribute_name]
95
- if array.empty?
96
- # Possibly an attribute, which must be retrieved in several ranges
91
+ def retrieve_array_attribute(entry, attribute_name)
92
+ array = entry[attribute_name]
93
+ if array.empty?
94
+ # Possibly an attribute, which must be retrieved in several ranges
97
95
 
98
- ranged_attr = entry.attribute_names.find { |n| n =~ /\A#{Regexp.escape(attribute_name)};range=/ }
99
- if ranged_attr
100
- entry_dn = entry.dn
96
+ ranged_attr = entry.attribute_names.find { |n| n =~ /\A#{Regexp.escape(attribute_name)};range=/ }
97
+ if ranged_attr
98
+ entry_dn = entry.dn
101
99
 
102
- loop do
103
- array += entry[ranged_attr]
104
- log.debug "retrieved attribute range #{ranged_attr.inspect} of dn #{entry_dn}"
100
+ loop do
101
+ array += entry[ranged_attr]
102
+ log.debug "retrieved attribute range #{ranged_attr.inspect} of dn #{entry_dn}"
105
103
 
106
- if ranged_attr =~ /;range=\d+\-\*\z/
107
- break
108
- end
104
+ if ranged_attr =~ /;range=\d+-\*\z/
105
+ break
106
+ end
109
107
 
110
- attribute_with_range = ranged_attr.to_s.gsub(/;range=.*/, ";range=#{array.size}-*")
111
- entry = @ldap.search(
112
- base: entry_dn,
113
- scope: Net::LDAP::SearchScope_BaseObject,
114
- attributes: attribute_with_range).first
108
+ attribute_with_range = ranged_attr.to_s.gsub(/;range=.*/, ";range=#{array.size}-*")
109
+ entry = @ldap.search(
110
+ base: entry_dn,
111
+ scope: Net::LDAP::SearchScope_BaseObject,
112
+ attributes: attribute_with_range
113
+ ).first
115
114
 
116
- ranged_attr = entry.attribute_names.find { |n| n =~ /\A#{Regexp.escape(attribute_name)};range=/ }
115
+ ranged_attr = entry.attribute_names.find { |n| n =~ /\A#{Regexp.escape(attribute_name)};range=/ }
116
+ end
117
117
  end
118
+ else
119
+ # Values already received -> No ranged attribute
118
120
  end
119
- else
120
- # Values already received -> No ranged attribute
121
+ array
121
122
  end
122
- return array
123
- end
124
123
 
125
- def search_ldap_groups
126
- ldap_group_conf = @config[:ldap_groups]
127
- name_attribute = ldap_group_conf[:name_attribute]
128
- member_attribute = ldap_group_conf[:member_attribute]
129
-
130
- groups = []
131
- res = @ldap.search(
132
- base: ldap_group_conf[:base],
133
- filter: ldap_group_conf[:filter],
134
- attributes: [name_attribute, member_attribute, :dn]
135
- ) do |entry|
136
- name = entry[name_attribute].first
137
-
138
- unless name
139
- log.warn "user attribute #{name_attribute.inspect} not defined for #{entry.dn}"
140
- next
141
- end
124
+ def search_ldap_groups
125
+ ldap_group_conf = @config[:ldap_groups]
126
+ name_attribute = ldap_group_conf[:name_attribute]
127
+ member_attribute = ldap_group_conf[:member_attribute]
128
+
129
+ groups = []
130
+ res = @ldap.search(
131
+ base: ldap_group_conf[:base],
132
+ filter: ldap_group_conf[:filter],
133
+ attributes: [name_attribute, member_attribute, :dn]
134
+ ) do |entry|
135
+ name = entry[name_attribute].first
136
+
137
+ unless name
138
+ log.warn "user attribute #{name_attribute.inspect} not defined for #{entry.dn}"
139
+ next
140
+ end
142
141
 
143
- log.info "found group-dn: #{entry.dn}"
142
+ log.info "found group-dn: #{entry.dn}"
144
143
 
145
- names = if ldap_group_conf[:bothcase_name]
146
- [name, name.downcase].uniq
147
- elsif ldap_group_conf[:lowercase_name]
148
- [name.downcase]
149
- else
150
- [name]
151
- end
144
+ names = if ldap_group_conf[:bothcase_name]
145
+ [name, name.downcase].uniq
146
+ elsif ldap_group_conf[:lowercase_name]
147
+ [name.downcase]
148
+ else
149
+ [name]
150
+ end
152
151
 
153
- names.each do |n|
154
- group_members = retrieve_array_attribute(entry, member_attribute)
155
- groups << LdapRole.new(n, entry.dn, group_members)
156
- end
157
- entry.each do |attribute, values|
158
- log.debug " #{attribute}:"
159
- values.each do |value|
160
- log.debug " --->#{value.inspect}"
152
+ names.each do |n|
153
+ group_members = retrieve_array_attribute(entry, member_attribute)
154
+ groups << LdapRole.new(n, entry.dn, group_members)
155
+ end
156
+ entry.each do |attribute, values|
157
+ log.debug " #{attribute}:"
158
+ values.each do |value|
159
+ log.debug " --->#{value.inspect}"
160
+ end
161
161
  end
162
162
  end
163
+ raise LdapError, "LDAP: #{@ldap.get_operation_result.message}" unless res
164
+ groups
163
165
  end
164
- raise LdapError, "LDAP: #{@ldap.get_operation_result.message}" unless res
165
- return groups
166
- end
167
166
 
168
- PgRole = Struct.new :name, :member_names
167
+ PgRole = Struct.new :name, :member_names
169
168
 
170
- # List of default roles taken from https://www.postgresql.org/docs/current/predefined-roles.html
171
- PG_BUILTIN_ROLES = %w[ pg_read_all_data pg_write_all_data pg_read_all_settings pg_read_all_stats pg_stat_scan_tables pg_monitor pg_database_owner pg_signal_backend pg_read_server_files pg_write_server_files pg_execute_server_program pg_checkpoint pg_create_subscription pg_maintain pg_use_reserved_connections ]
169
+ # List of default roles taken from https://www.postgresql.org/docs/current/predefined-roles.html
170
+ PG_BUILTIN_ROLES = %w[pg_read_all_data pg_write_all_data pg_read_all_settings pg_read_all_stats pg_stat_scan_tables pg_monitor pg_database_owner pg_signal_backend pg_read_server_files pg_write_server_files pg_execute_server_program pg_checkpoint pg_create_subscription pg_maintain pg_use_reserved_connections]
172
171
 
173
- def search_pg_users
174
- pg_users_conf = @config[:pg_users]
172
+ def search_pg_users
173
+ pg_users_conf = @config[:pg_users]
175
174
 
176
- users = []
177
- res = pg_exec "SELECT rolname FROM pg_roles WHERE #{pg_users_conf[:filter]}"
178
- res.each do |tuple|
179
- user = PgRole.new tuple[0]
180
- next if PG_BUILTIN_ROLES.include?(user.name)
181
- log.info{ "found pg-user: #{user.name.inspect}"}
182
- users << user
175
+ users = []
176
+ res = pg_exec "SELECT rolname FROM pg_roles WHERE #{pg_users_conf[:filter]}"
177
+ res.each do |tuple|
178
+ user = PgRole.new tuple[0]
179
+ next if PG_BUILTIN_ROLES.include?(user.name)
180
+ log.info { "found pg-user: #{user.name.inspect}" }
181
+ users << user
182
+ end
183
+ users
183
184
  end
184
- return users
185
- end
186
185
 
187
- def search_pg_groups
188
- pg_groups_conf = @config[:pg_groups]
189
-
190
- groups = []
191
- res = pg_exec "SELECT rolname, oid FROM pg_roles WHERE #{pg_groups_conf[:filter]}"
192
- res.each do |tuple|
193
- res2 = pg_exec "SELECT pr.rolname FROM pg_auth_members pam JOIN pg_roles pr ON pr.oid=pam.member WHERE pam.roleid=#{@pgconn.escape_string(tuple[1])}"
194
- member_names = res2.map{|row| row[0] }
195
- group = PgRole.new tuple[0], member_names
196
- next if PG_BUILTIN_ROLES.include?(group.name)
197
- log.info{ "found pg-group: #{group.name.inspect} with members: #{member_names.inspect}"}
198
- groups << group
186
+ def search_pg_groups
187
+ pg_groups_conf = @config[:pg_groups]
188
+
189
+ groups = []
190
+ res = pg_exec "SELECT rolname, oid FROM pg_roles WHERE #{pg_groups_conf[:filter]}"
191
+ res.each do |tuple|
192
+ res2 = pg_exec "SELECT pr.rolname FROM pg_auth_members pam JOIN pg_roles pr ON pr.oid=pam.member WHERE pam.roleid=#{@pgconn.escape_string(tuple[1])}"
193
+ member_names = res2.map { |row| row[0] }
194
+ group = PgRole.new tuple[0], member_names
195
+ next if PG_BUILTIN_ROLES.include?(group.name)
196
+ log.info { "found pg-group: #{group.name.inspect} with members: #{member_names.inspect}" }
197
+ groups << group
198
+ end
199
+ groups
199
200
  end
200
- return groups
201
- end
202
201
 
203
- def uniq_names(list)
204
- names = {}
205
- new_list = list.select do |entry|
206
- name = entry.name
207
- if names[name]
208
- log.warn{ "duplicated group/user #{name.inspect} (#{entry.inspect})" }
209
- next false
210
- else
211
- names[name] = true
212
- next true
202
+ def uniq_names(list)
203
+ names = {}
204
+ list.select do |entry|
205
+ name = entry.name
206
+ if names[name]
207
+ log.warn { "duplicated group/user #{name.inspect} (#{entry.inspect})" }
208
+ next false
209
+ else
210
+ names[name] = true
211
+ next true
212
+ end
213
213
  end
214
214
  end
215
- return new_list
216
- end
217
-
218
- MatchedRole = Struct.new :ldap, :pg, :name, :state, :type
219
215
 
220
- def match_roles(ldaps, pgs, type)
221
- ldap_by_name = ldaps.inject({}){|h,u| h[u.name] = u; h }
222
- pg_by_name = pgs.inject({}){|h,u| h[u.name] = u; h }
223
-
224
- roles = []
225
- ldaps.each do |ld|
226
- pg = pg_by_name[ld.name]
227
- role = MatchedRole.new ld, pg, ld.name
228
- roles << role
229
- end
230
- pgs.each do |pg|
231
- ld = ldap_by_name[pg.name]
232
- next if ld
233
- role = MatchedRole.new ld, pg, pg.name
234
- roles << role
235
- end
216
+ MatchedRole = Struct.new :ldap, :pg, :name, :state, :type
217
+
218
+ def match_roles(ldaps, pgs, type)
219
+ ldap_by_name = ldaps.each_with_object({}) { |u, h|
220
+ h[u.name] = u
221
+ }
222
+ pg_by_name = pgs.each_with_object({}) { |u, h|
223
+ h[u.name] = u
224
+ }
225
+
226
+ roles = []
227
+ ldaps.each do |ld|
228
+ pg = pg_by_name[ld.name]
229
+ role = MatchedRole.new ld, pg, ld.name
230
+ roles << role
231
+ end
232
+ pgs.each do |pg|
233
+ ld = ldap_by_name[pg.name]
234
+ next if ld
235
+ role = MatchedRole.new ld, pg, pg.name
236
+ roles << role
237
+ end
236
238
 
237
- roles.each do |r|
238
- r.state = case
239
+ roles.each do |r|
240
+ r.state = case
239
241
  when r.ldap && !r.pg then :create
240
242
  when !r.ldap && r.pg then :drop
241
243
  when r.pg && r.ldap then :keep
242
244
  else raise "invalid user #{r.inspect}"
245
+ end
246
+ r.type = type
243
247
  end
244
- r.type = type
245
- end
246
248
 
247
- log.info do
248
- roles.each do |role|
249
- log.debug{ "#{role.state} #{role.type}: #{role.name}" }
249
+ log.info do
250
+ roles.each do |role|
251
+ log.debug { "#{role.state} #{role.type}: #{role.name}" }
252
+ end
253
+ "#{type} stat: create: #{roles.count { |r| r.state == :create }} drop: #{roles.count { |r| r.state == :drop }} keep: #{roles.count { |r| r.state == :keep }}"
250
254
  end
251
- "#{type} stat: create: #{roles.count{|r| r.state==:create }} drop: #{roles.count{|r| r.state==:drop }} keep: #{roles.count{|r| r.state==:keep }}"
255
+ roles
252
256
  end
253
- return roles
254
- end
255
257
 
256
- def try_sql(text)
257
- begin
258
- @pgconn.exec "SAVEPOINT try_sql;"
259
- @pgconn.exec text
260
- rescue PG::Error => err
261
- @pgconn.exec "ROLLBACK TO try_sql;"
258
+ def try_sql(text)
259
+ begin
260
+ @pgconn.exec "SAVEPOINT try_sql;"
261
+ @pgconn.exec text
262
+ rescue PG::Error => err
263
+ @pgconn.exec "ROLLBACK TO try_sql;"
262
264
 
263
- log.error{ "#{err} (#{err.class})" }
265
+ log.error { "#{err} (#{err.class})" }
266
+ end
264
267
  end
265
- end
266
268
 
267
- def pg_exec_modify(sql)
268
- log.info{ "SQL: #{sql}" }
269
- unless self.test
270
- try_sql sql
269
+ def pg_exec_modify(sql)
270
+ log.info { "SQL: #{sql}" }
271
+ unless self.test
272
+ try_sql sql
273
+ end
271
274
  end
272
- end
273
275
 
274
- def pg_exec(sql)
275
- res = @pgconn.exec sql
276
- (0...res.num_tuples).map{|t| (0...res.num_fields).map{|i| res.getvalue(t, i) } }
277
- end
276
+ def pg_exec(sql)
277
+ res = @pgconn.exec sql
278
+ (0...res.num_tuples).map { |t| (0...res.num_fields).map { |i| res.getvalue(t, i) } }
279
+ end
278
280
 
279
- def create_pg_role(role)
280
- pg_conf = @config[role.type==:user ? :pg_users : :pg_groups]
281
- pg_exec_modify "CREATE ROLE \"#{role.name}\" #{pg_conf[:create_options]}"
282
- end
281
+ def create_pg_role(role)
282
+ pg_conf = @config[(role.type == :user) ? :pg_users : :pg_groups]
283
+ pg_exec_modify "CREATE ROLE \"#{role.name}\" #{pg_conf[:create_options]}"
284
+ end
283
285
 
284
- def drop_pg_role(role)
285
- pg_exec_modify "DROP ROLE \"#{role.name}\""
286
- end
286
+ def drop_pg_role(role)
287
+ pg_exec_modify "DROP ROLE \"#{role.name}\""
288
+ end
287
289
 
288
- def sync_roles_to_pg(roles, for_state)
289
- roles.sort{|a,b| a.name<=>b.name }.each do |role|
290
- create_pg_role(role) if role.state==:create && for_state==:create
291
- drop_pg_role(role) if role.state==:drop && for_state==:drop
290
+ def sync_roles_to_pg(roles, for_state)
291
+ roles.sort_by(&:name).each do |role|
292
+ create_pg_role(role) if role.state == :create && for_state == :create
293
+ drop_pg_role(role) if role.state == :drop && for_state == :drop
294
+ end
292
295
  end
293
- end
294
296
 
295
- MatchedMembership = Struct.new :role_name, :has_member, :state
296
-
297
- def match_memberships(ldap_roles, pg_roles)
298
- hash_of_arrays = Hash.new { |h, k| h[k] = [] }
299
- ldap_by_dn = ldap_roles.inject(hash_of_arrays){|h,r| h[r.dn] << r; h }
300
- ldap_by_m2m = ldap_roles.inject([]) do |a,r|
301
- next a unless r.member_dns
302
- a + r.member_dns.flat_map do |dn|
303
- has_members = ldap_by_dn[dn]
304
- log.warn{"ldap member with dn #{dn} is unknown"} if has_members.empty?
305
- has_members.map do |has_member|
306
- [r.name, has_member.name]
297
+ MatchedMembership = Struct.new :role_name, :has_member, :state
298
+
299
+ def match_memberships(ldap_roles, pg_roles)
300
+ hash_of_arrays = Hash.new { |h, k| h[k] = [] }
301
+ ldap_by_dn = ldap_roles.each_with_object(hash_of_arrays) { |r, h|
302
+ h[r.dn] << r
303
+ }
304
+ ldap_by_m2m = ldap_roles.inject([]) do |a, r|
305
+ next a unless r.member_dns
306
+ a + r.member_dns.flat_map do |dn|
307
+ has_members = ldap_by_dn[dn]
308
+ log.warn { "ldap member with dn #{dn} is unknown" } if has_members.empty?
309
+ has_members.map do |has_member|
310
+ [r.name, has_member.name]
311
+ end
307
312
  end
308
313
  end
309
- end
310
314
 
311
- hash_of_arrays = Hash.new { |h, k| h[k] = [] }
312
- pg_by_name = pg_roles.inject(hash_of_arrays){|h,r| h[r.name] << r; h }
313
- pg_by_m2m = pg_roles.inject([]) do |a,r|
314
- next a unless r.member_names
315
- a + r.member_names.flat_map do |name|
316
- has_members = pg_by_name[name]
317
- log.warn{"pg member with name #{name} is unknown"} if has_members.empty?
318
- has_members.map do |has_member|
319
- [r.name, has_member.name]
315
+ hash_of_arrays = Hash.new { |h, k| h[k] = [] }
316
+ pg_by_name = pg_roles.each_with_object(hash_of_arrays) { |r, h|
317
+ h[r.name] << r
318
+ }
319
+ pg_by_m2m = pg_roles.inject([]) do |a, r|
320
+ next a unless r.member_names
321
+ a + r.member_names.flat_map do |name|
322
+ has_members = pg_by_name[name]
323
+ log.warn { "pg member with name #{name} is unknown" } if has_members.empty?
324
+ has_members.map do |has_member|
325
+ [r.name, has_member.name]
326
+ end
320
327
  end
321
328
  end
322
- end
323
329
 
324
- memberships = (ldap_by_m2m & pg_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :keep }
325
- memberships += (ldap_by_m2m - pg_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :grant }
326
- memberships += (pg_by_m2m - ldap_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :revoke }
330
+ memberships = (ldap_by_m2m & pg_by_m2m).map { |r, mo| MatchedMembership.new r, mo, :keep }
331
+ memberships += (ldap_by_m2m - pg_by_m2m).map { |r, mo| MatchedMembership.new r, mo, :grant }
332
+ memberships += (pg_by_m2m - ldap_by_m2m).map { |r, mo| MatchedMembership.new r, mo, :revoke }
327
333
 
328
- log.info do
329
- memberships.each do |membership|
330
- log.debug{ "#{membership.state} #{membership.role_name} to #{membership.has_member}" }
334
+ log.info do
335
+ memberships.each do |membership|
336
+ log.debug { "#{membership.state} #{membership.role_name} to #{membership.has_member}" }
337
+ end
338
+ "membership stat: grant: #{memberships.count { |u| u.state == :grant }} revoke: #{memberships.count { |u| u.state == :revoke }} keep: #{memberships.count { |u| u.state == :keep }}"
331
339
  end
332
- "membership stat: grant: #{memberships.count{|u| u.state==:grant }} revoke: #{memberships.count{|u| u.state==:revoke }} keep: #{memberships.count{|u| u.state==:keep }}"
340
+ memberships
333
341
  end
334
- return memberships
335
- end
336
-
337
- def grant_membership(role_name, add_members)
338
- pg_conf = @config[:pg_groups]
339
- add_members_escaped = add_members.map{|m| "\"#{m}\"" }.join(",")
340
- pg_exec_modify "GRANT \"#{role_name}\" TO #{add_members_escaped} #{pg_conf[:grant_options]}"
341
- end
342
342
 
343
- def revoke_membership(role_name, rm_members)
344
- rm_members_escaped = rm_members.map{|m| "\"#{m}\"" }.join(",")
345
- pg_exec_modify "REVOKE \"#{role_name}\" FROM #{rm_members_escaped}"
346
- end
343
+ def grant_membership(role_name, add_members)
344
+ pg_conf = @config[:pg_groups]
345
+ add_members_escaped = add_members.map { |m| "\"#{m}\"" }.join(",")
346
+ pg_exec_modify "GRANT \"#{role_name}\" TO #{add_members_escaped} #{pg_conf[:grant_options]}"
347
+ end
347
348
 
348
- def sync_membership_to_pg(memberships, for_state)
349
- grants = {}
350
- memberships.select{|ms| ms.state==for_state }.each do |ms|
351
- grants[ms.role_name] ||= []
352
- grants[ms.role_name] << ms.has_member
349
+ def revoke_membership(role_name, rm_members)
350
+ rm_members_escaped = rm_members.map { |m| "\"#{m}\"" }.join(",")
351
+ pg_exec_modify "REVOKE \"#{role_name}\" FROM #{rm_members_escaped}"
353
352
  end
354
353
 
355
- grants.each do |role_name, members|
356
- grant_membership(role_name, members) if for_state==:grant
357
- revoke_membership(role_name, members) if for_state==:revoke
354
+ def sync_membership_to_pg(memberships, for_state)
355
+ grants = {}
356
+ memberships.select { |ms| ms.state == for_state }.each do |ms|
357
+ grants[ms.role_name] ||= []
358
+ grants[ms.role_name] << ms.has_member
359
+ end
360
+
361
+ grants.each do |role_name, members|
362
+ grant_membership(role_name, members) if for_state == :grant
363
+ revoke_membership(role_name, members) if for_state == :revoke
364
+ end
358
365
  end
359
- end
360
366
 
361
- def start!
362
- read_config_file(@config_fname)
367
+ def start!
368
+ read_config_file(@config_fname)
363
369
 
364
- ldap_conf = @config[:ldap_connection]
365
- auth_meth = ldap_conf.dig(:auth, :method).to_s
366
- if auth_meth == "gssapi"
367
- begin
368
- require 'net/ldap/auth_adapter/gssapi'
369
- rescue LoadError => err
370
- raise "#{err}\nTo use GSSAPI authentication please run:\n gem install net-ldap-auth_adapter-gssapi"
370
+ ldap_conf = @config[:ldap_connection]
371
+ auth_meth = ldap_conf.dig(:auth, :method).to_s
372
+ if auth_meth == "gssapi"
373
+ begin
374
+ require "net/ldap/auth_adapter/gssapi"
375
+ rescue LoadError => err
376
+ raise "#{err}\nTo use GSSAPI authentication please run:\n gem install net-ldap-auth_adapter-gssapi"
377
+ end
378
+ elsif auth_meth == "gss_spnego"
379
+ begin
380
+ require "net-ldap-gss-spnego"
381
+ # This doesn't work since this file is defined in net-ldap as a placeholder:
382
+ # require 'net/ldap/auth_adapter/gss_spnego'
383
+ rescue LoadError => err
384
+ raise "#{err}\nTo use GSSAPI authentication please run:\n gem install net-ldap-gss-spnego"
385
+ end
371
386
  end
372
- elsif auth_meth == "gss_spnego"
387
+
388
+ # gather LDAP users and groups
389
+ @ldap = Net::LDAP.new ldap_conf
390
+ ldap_users = uniq_names search_ldap_users
391
+ ldap_groups = uniq_names search_ldap_groups
392
+
393
+ # gather PGs users and groups
394
+ @pgconn = PG.connect @config[:pg_connection]
373
395
  begin
374
- require 'net-ldap-gss-spnego'
375
- # This doesn't work since this file is defined in net-ldap as a placeholder:
376
- # require 'net/ldap/auth_adapter/gss_spnego'
377
- rescue LoadError => err
378
- raise "#{err}\nTo use GSSAPI authentication please run:\n gem install net-ldap-gss-spnego"
396
+ @pgconn.transaction do
397
+ pg_users = uniq_names search_pg_users
398
+ pg_groups = uniq_names search_pg_groups
399
+
400
+ # compare LDAP to PG users and groups
401
+ mroles = match_roles(ldap_users, pg_users, :user)
402
+ mroles += match_roles(ldap_groups, pg_groups, :group)
403
+
404
+ # compare LDAP to PG memberships
405
+ mmemberships = match_memberships(ldap_users + ldap_groups, pg_users + pg_groups)
406
+
407
+ # drop/revoke roles/memberships first
408
+ sync_membership_to_pg(mmemberships, :revoke)
409
+ sync_roles_to_pg(mroles, :drop)
410
+ # create/grant roles/memberships
411
+ sync_roles_to_pg(mroles, :create)
412
+ sync_membership_to_pg(mmemberships, :grant)
413
+ end
414
+ ensure
415
+ @pgconn.close
379
416
  end
380
- end
381
417
 
382
- # gather LDAP users and groups
383
- @ldap = Net::LDAP.new ldap_conf
384
- ldap_users = uniq_names search_ldap_users
385
- ldap_groups = uniq_names search_ldap_groups
386
-
387
- # gather PGs users and groups
388
- @pgconn = PG.connect @config[:pg_connection]
389
- begin
390
- @pgconn.transaction do
391
- pg_users = uniq_names search_pg_users
392
- pg_groups = uniq_names search_pg_groups
393
-
394
- # compare LDAP to PG users and groups
395
- mroles = match_roles(ldap_users, pg_users, :user)
396
- mroles += match_roles(ldap_groups, pg_groups, :group)
397
-
398
- # compare LDAP to PG memberships
399
- mmemberships = match_memberships(ldap_users+ldap_groups, pg_users+pg_groups)
400
-
401
- # drop/revoke roles/memberships first
402
- sync_membership_to_pg(mmemberships, :revoke)
403
- sync_roles_to_pg(mroles, :drop)
404
- # create/grant roles/memberships
405
- sync_roles_to_pg(mroles, :create)
406
- sync_membership_to_pg(mmemberships, :grant)
418
+ # Determine exitcode
419
+ if log.had_errors?
420
+ raise ErrorExit.new(1, log.first_error)
407
421
  end
408
- ensure
409
- @pgconn.close
410
422
  end
411
423
 
412
- # Determine exitcode
413
- if log.had_errors?
414
- raise ErrorExit.new(1, log.first_error)
415
- end
416
- end
424
+ def self.run(argv)
425
+ s = new
426
+ s.config_fname = "/etc/pg_ldap_sync.yaml"
427
+ s.log = Logger.new($stdout)
428
+ s.log.level = Logger::ERROR
417
429
 
418
- def self.run(argv)
419
- s = self.new
420
- s.config_fname = '/etc/pg_ldap_sync.yaml'
421
- s.log = Logger.new($stdout)
422
- s.log.level = Logger::ERROR
430
+ OptionParser.new do |opts|
431
+ opts.version = VERSION
432
+ opts.banner = "Usage: #{$0} [options]"
433
+ opts.on("-v", "--[no-]verbose", "Increase verbose level") { |v| s.log.level += v ? -1 : 1 }
434
+ opts.on("-c", "--config FILE", "Config file [#{s.config_fname}]", &s.method(:config_fname=))
435
+ opts.on("-t", "--[no-]test", "Don't do any change in the database", &s.method(:test=))
423
436
 
424
- OptionParser.new do |opts|
425
- opts.version = VERSION
426
- opts.banner = "Usage: #{$0} [options]"
427
- opts.on("-v", "--[no-]verbose", "Increase verbose level"){|v| s.log.level += v ? -1 : 1 }
428
- opts.on("-c", "--config FILE", "Config file [#{s.config_fname}]", &s.method(:config_fname=))
429
- opts.on("-t", "--[no-]test", "Don't do any change in the database", &s.method(:test=))
437
+ opts.parse!(argv)
438
+ end
430
439
 
431
- opts.parse!(argv)
440
+ s.start!
432
441
  end
433
-
434
- s.start!
435
442
  end
436
443
  end
437
- end