rubysync 0.0.3 → 0.0.4
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.
- data/HISTORY.txt +4 -0
- data/Manifest.txt +25 -12
- data/README.txt +0 -2
- data/bin/rubysync +20 -6
- data/bin/rubysync.rb +333 -0
- data/docs/in_pipeline.graffle +2690 -0
- data/docs/init_openldap.ldif +11 -0
- data/docs/out_pipeline.graffle +3274 -0
- data/docs/schema/99rubysync.ldif +27 -0
- data/docs/schema/rubysync.schema +16 -0
- data/docs/to_sync.txt +15 -0
- data/docs/walkthru.txt +186 -0
- data/lib/ruby_sync.rb +7 -29
- data/lib/ruby_sync/connectors/base_connector.rb +55 -86
- data/lib/ruby_sync/connectors/csv_file_connector.rb +16 -4
- data/lib/ruby_sync/connectors/ldap_associations.rb +126 -0
- data/lib/ruby_sync/connectors/ldap_changelog_connector.rb +127 -0
- data/lib/ruby_sync/connectors/ldap_connector.rb +29 -192
- data/lib/ruby_sync/connectors/memory_connector.rb +1 -1
- data/lib/ruby_sync/connectors/xml_connector.rb +105 -32
- data/lib/ruby_sync/event.rb +40 -12
- data/lib/ruby_sync/operation.rb +18 -2
- data/lib/ruby_sync/pipelines/base_pipeline.rb +44 -6
- data/lib/ruby_sync/util/utilities.rb +97 -4
- data/lib/rubysync.rb +1 -1
- data/rubysync.tmproj +279 -59
- data/test/.LCKts_rubysync.rb~ +1 -0
- data/test/ruby_sync_test.rb +9 -4
- data/test/{test_active_record_vault.rb → tc_active_record_connector.rb} +11 -7
- data/test/{test_base_connector.rb → tc_base_connector.rb} +1 -1
- data/test/{test_base_pipeline.rb → tc_base_pipeline.rb} +1 -1
- data/test/tc_changelog_ldap_connector.rb +93 -0
- data/test/{test_csv_file_connector.rb → tc_csv_file_connector.rb} +14 -5
- data/test/{test_event.rb → tc_event.rb} +1 -1
- data/test/{test_ldap_changelog.rb → tc_ldap_changelog.rb} +1 -1
- data/test/{test_ldap_connector.rb → tc_ldap_connector.rb} +20 -22
- data/test/{test_ldap_vault.rb → tc_ldap_vault.rb} +2 -2
- data/test/{test_ldif.rb → tc_ldif.rb} +1 -1
- data/test/{test_memory_connectors.rb → tc_memory_connectors.rb} +10 -6
- data/test/{test_rubysync.rb → tc_rubysync.rb} +4 -4
- data/test/tc_transformation.rb +71 -0
- data/test/{test_utilities.rb → tc_utilities.rb} +28 -1
- data/test/tc_xml_connectors.rb +107 -6
- data/test/ts_rubysync.rb +11 -6
- metadata +33 -28
@@ -17,17 +17,25 @@ module RubySync
|
|
17
17
|
class CsvFileConnector < RubySync::Connectors::FileConnector
|
18
18
|
|
19
19
|
option :field_names, # A list of names representing the namesspace for this connector
|
20
|
-
:path_field # The name of the field to use as the source_path
|
20
|
+
:path_field, # The name of the field to use as the source_path
|
21
|
+
:header_line # true if the first line is a header and should be ignored during imports
|
21
22
|
|
22
23
|
in_glob '*.csv'
|
23
24
|
out_extension '.csv'
|
24
25
|
field_names []
|
25
|
-
path_field
|
26
|
+
path_field((get_field_names.empty?)? 'field_0': @field_names[0])
|
27
|
+
|
26
28
|
|
27
29
|
# Called for each filename matching in_glob in in_path
|
28
30
|
# Yields a modify event for each row found in the file.
|
29
31
|
def each_file_change(filename)
|
32
|
+
header = header_line
|
30
33
|
CSV.open(filename, 'r') do |row|
|
34
|
+
if header # should we ignore the first line
|
35
|
+
header = false
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
31
39
|
if defined? field_name &&row.length > field_names.length
|
32
40
|
log.warn "#{name}: Row in file #{filename} exceeds defined field_names"
|
33
41
|
end
|
@@ -35,7 +43,7 @@ module RubySync
|
|
35
43
|
data = {}
|
36
44
|
row.each_index do |i|
|
37
45
|
field_name = (i < field_names.length)? field_names[i] : "field_#{i}"
|
38
|
-
data[field_name] = row[i].data
|
46
|
+
row[i] and data[field_name] = row[i].data
|
39
47
|
end
|
40
48
|
association_key = source_path = path_for(data)
|
41
49
|
yield RubySync::Event.modify(self, source_path, association_key, create_operations_for(data))
|
@@ -44,7 +52,11 @@ module RubySync
|
|
44
52
|
|
45
53
|
def self.sample_config
|
46
54
|
return <<END
|
47
|
-
|
55
|
+
|
56
|
+
# True if the first line of each file is a header
|
57
|
+
# and should be ignored
|
58
|
+
header_line true
|
59
|
+
|
48
60
|
field_names ['names', 'of', 'the', 'columns']
|
49
61
|
path_field 'name_of_field_to_use_as_the_id'
|
50
62
|
in_path '/directory/to/read/files/from'
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (c) 2007 Ritchie Young. All rights reserved.
|
4
|
+
#
|
5
|
+
# This file is part of RubySync.
|
6
|
+
#
|
7
|
+
# RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
|
8
|
+
# as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
11
|
+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
|
14
|
+
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
15
|
+
|
16
|
+
|
17
|
+
lib_path = File.dirname(__FILE__) + '/..'
|
18
|
+
$:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
|
19
|
+
|
20
|
+
require 'ruby_sync'
|
21
|
+
require 'ldap_connector'
|
22
|
+
$VERBOSE = false
|
23
|
+
require 'net/ldap'
|
24
|
+
$VERBOSE = true
|
25
|
+
|
26
|
+
|
27
|
+
RUBYSYNC_ASSOCIATION_ATTRIBUTE = "RubySyncAssociation"
|
28
|
+
RUBYSYNC_ASSOCIATION_CLASS = "RubySyncSynchable"
|
29
|
+
|
30
|
+
|
31
|
+
module LdapAssociations
|
32
|
+
|
33
|
+
|
34
|
+
def associate association, path
|
35
|
+
with_ldap do |ldap|
|
36
|
+
# todo: check and warn if path is outside of search_base
|
37
|
+
ldap.modify :dn=>path, :operations=>[
|
38
|
+
[:add, RUBYSYNC_ASSOCIATION_ATTRIBUTE, association.to_s]
|
39
|
+
]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def path_for_association association
|
44
|
+
with_ldap do |ldap|
|
45
|
+
filter = "#{RUBYSYNC_ASSOCIATION_ATTRIBUTE}=#{association.to_s}"
|
46
|
+
log.debug "Searching with filter: #{filter}"
|
47
|
+
results = ldap.search :base=>@search_base,
|
48
|
+
:filter=>filter,
|
49
|
+
:attributes=>[]
|
50
|
+
results or return nil
|
51
|
+
case results.length
|
52
|
+
when 0: return nil
|
53
|
+
when 1: return results[0].dn
|
54
|
+
else
|
55
|
+
raise Exception.new("Duplicate association found for #{association.to_s}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def associations_for path
|
61
|
+
with_ldap do |ldap|
|
62
|
+
results = ldap.search :base=>path,
|
63
|
+
:scope=>Net::LDAP::SearchScope_BaseObject,
|
64
|
+
:attributes=>[RUBYSYNC_ASSOCIATION_ATTRIBUTE]
|
65
|
+
unless results and results.length > 0
|
66
|
+
log.warn "Attempted association lookup on non-existent LDAP entry '#{path}'"
|
67
|
+
return []
|
68
|
+
end
|
69
|
+
associations = results[0][RUBYSYNC_ASSOCIATION_ATTRIBUTE]
|
70
|
+
return (associations)? as_array(associations) : []
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove_association association
|
75
|
+
path = path_for_association association
|
76
|
+
with_ldap do |ldap|
|
77
|
+
ldap.modify :dn=>path, :modifications=>[
|
78
|
+
[:delete, RUBYSYNC_ASSOCIATION_ATTRIBUTE, association.to_s]
|
79
|
+
]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# def associate_with_foreign_key key, path
|
85
|
+
# with_ldap do |ldap|
|
86
|
+
# ldap.add_attribute(path, association_attribute, key.to_s)
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# def path_for_foreign_key key
|
91
|
+
# entry = entry_for_foreign_key key
|
92
|
+
# (entry)? entry.dn : nil
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# def foreign_key_for path
|
96
|
+
# entry = self[path]
|
97
|
+
# (entry)? entry.dn : nil # TODO: That doesn't look right. Should return an association key, not a path.
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# def remove_foreign_key key
|
101
|
+
# with_ldap do |ldap|
|
102
|
+
# entry = entry_for_foreign_key key
|
103
|
+
# if entry
|
104
|
+
# modify :dn=>entry.dn, :operations=>[ [:delete, association_attribute, key] ]
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# def find_associated foreign_key
|
110
|
+
# entry = entry_for_foreign_key key
|
111
|
+
# (entry)? operations_for_entry(entry) : nil
|
112
|
+
# end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def entry_for_foreign_key key
|
117
|
+
with_ldap do |ldap|
|
118
|
+
result = ldap.search :base=>search_base, :filter=>"#{association_attribute}=#{key}"
|
119
|
+
return nil if !result or result.size == 0
|
120
|
+
result[0]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
|
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (c) 2007 Ritchie Young. All rights reserved.
|
4
|
+
#
|
5
|
+
# This file is part of RubySync.
|
6
|
+
#
|
7
|
+
# RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
|
8
|
+
# as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
11
|
+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
|
14
|
+
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
15
|
+
|
16
|
+
|
17
|
+
lib_path = File.dirname(__FILE__) + '/..'
|
18
|
+
$:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
|
19
|
+
|
20
|
+
require 'ruby_sync'
|
21
|
+
require 'ldap_connector'
|
22
|
+
require 'net/ldif'
|
23
|
+
$VERBOSE = false
|
24
|
+
require 'net/ldap'
|
25
|
+
#$VERBOSE = true
|
26
|
+
|
27
|
+
class LdapChangelogConnector < RubySync::Connectors::LdapConnector
|
28
|
+
|
29
|
+
option :changelog_dn
|
30
|
+
changelog_dn "cn=changelog"
|
31
|
+
|
32
|
+
def initialize options={}
|
33
|
+
super options
|
34
|
+
@last_change_number = 1
|
35
|
+
# TODO: Persist the current CSN, for now we'll just skip to the end of the changelog
|
36
|
+
skip_existing_changelog_entries
|
37
|
+
end
|
38
|
+
# Look for changelog entries. This is not supported by all LDAP servers
|
39
|
+
# Changelog entries have these attributes
|
40
|
+
# targetdn
|
41
|
+
# changenumber
|
42
|
+
# objectclass
|
43
|
+
# changes
|
44
|
+
# changetime
|
45
|
+
# changetype
|
46
|
+
# dn
|
47
|
+
#
|
48
|
+
# TODO: Detect presence/location of changelog from root DSE
|
49
|
+
def each_change
|
50
|
+
with_ldap do |ldap|
|
51
|
+
log.debug "@last_change_number = #{@last_change_number}"
|
52
|
+
filter = "(changenumber>=#{@last_change_number})"
|
53
|
+
first = true
|
54
|
+
@full_refresh_required = false
|
55
|
+
ldap.search :base => changelog_dn, :filter =>filter do |change|
|
56
|
+
change_number = change.changenumber[0].to_i
|
57
|
+
if first
|
58
|
+
first = false
|
59
|
+
# TODO: Persist the change_number so that we don't do a full resync everytime rubysync starts
|
60
|
+
if change_number != @last_change_number
|
61
|
+
log.warn "Earliest change number (#{change_number}) differs from that recorded (#{@last_change_number})."
|
62
|
+
log.warn "A full refresh is required."
|
63
|
+
@full_refresh_required = true
|
64
|
+
break
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@last_change_number = change_number if change_number > @last_change_number
|
68
|
+
# todo: A proper DN object would be nice instead of string manipulation
|
69
|
+
target_dn = change.targetdn[0].gsub(/\s*,\s*/,',')
|
70
|
+
if target_dn =~ /#{search_base}$/oi
|
71
|
+
change_type = change.changetype[0]
|
72
|
+
event = event_for_changelog_entry(change)
|
73
|
+
yield event
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def skip_existing_changelog_entries
|
82
|
+
with_ldap do |ldap|
|
83
|
+
filter = "(changenumber>=#{@last_change_number})"
|
84
|
+
@full_refresh_required = false
|
85
|
+
ldap.search :base => changelog_dn, :filter =>filter do |change|
|
86
|
+
change_number = change.changenumber[0].to_i
|
87
|
+
@last_change_number = change_number if change_number > @last_change_number
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Called by unit tests to inject data
|
94
|
+
def test_add id, details
|
95
|
+
details << RubySync::Operation.new(:add, "objectclass", ['inetOrgPerson', 'organizationalPerson', 'person', 'top', 'RubySyncSynchable'])
|
96
|
+
add id, details
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
|
103
|
+
def event_for_changelog_entry cle
|
104
|
+
payload = nil
|
105
|
+
dn = cle.targetdn[0]
|
106
|
+
changetype = cle.changetype[0]
|
107
|
+
if cle.attribute_names.include? :changes
|
108
|
+
payload = []
|
109
|
+
cr = Net::LDIF.parse("dn: #{dn}\nchangetype: #{changetype}\n#{cle.changes[0]}")[0]
|
110
|
+
if changetype.to_sym == :add
|
111
|
+
# cr.data will be a hash of arrays or strings (attr-name=>[value1, value2, ...])
|
112
|
+
cr.data.each do |name, values|
|
113
|
+
payload << RubySync::Operation.add(name, values)
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# cr.data will be an array of arrays of form [:action, :subject, [values]]
|
117
|
+
cr.data.each do |record|
|
118
|
+
payload << RubySync::Operation.new(record[0], record[1], record[2])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
RubySync::Event.new(changetype, self, dn, nil, payload)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
|
@@ -23,8 +23,6 @@ $VERBOSE = false
|
|
23
23
|
require 'net/ldap'
|
24
24
|
#$VERBOSE = true
|
25
25
|
|
26
|
-
RUBYSYNC_ASSOCIATION_ATTRIBUTE = "RubySyncAssociation"
|
27
|
-
RUBYSYNC_ASSOCIATION_CLASS = "RubySyncSynchable"
|
28
26
|
|
29
27
|
class Net::LDAP::Entry
|
30
28
|
def to_hash
|
@@ -36,27 +34,22 @@ module RubySync::Connectors
|
|
36
34
|
class LdapConnector < RubySync::Connectors::BaseConnector
|
37
35
|
|
38
36
|
option :host,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
:changelog_dn
|
37
|
+
:port,
|
38
|
+
:bind_method,
|
39
|
+
:username,
|
40
|
+
:password,
|
41
|
+
:search_filter,
|
42
|
+
:search_base,
|
43
|
+
:association_attribute # name of the attribute in which to store the association key(s)
|
47
44
|
|
48
45
|
association_attribute 'RubySyncAssociation'
|
49
46
|
bind_method :simple
|
50
47
|
host 'localhost'
|
51
48
|
port 389
|
52
49
|
search_filter "cn=*"
|
53
|
-
changelog_dn "cn=changelog"
|
54
50
|
|
55
51
|
def initialize options={}
|
56
|
-
super options
|
57
|
-
@last_change_number = 1
|
58
|
-
# TODO: Persist the current CSN, for now we'll just skip to the end of the changelog
|
59
|
-
skip_existing_changelog_entries
|
52
|
+
super options
|
60
53
|
end
|
61
54
|
|
62
55
|
|
@@ -64,68 +57,11 @@ module RubySync::Connectors
|
|
64
57
|
#TODO: If vault, check the schema to make sure that the association_attribute is there
|
65
58
|
end
|
66
59
|
|
67
|
-
|
68
|
-
# Look for changelog entries. This is not supported by all LDAP servers
|
69
|
-
# you may need to subclass for OpenLDAP and Active Directory
|
70
|
-
# Changelog entries have these attributes
|
71
|
-
# targetdn
|
72
|
-
# changenumber
|
73
|
-
# objectclass
|
74
|
-
# changes
|
75
|
-
# changetime
|
76
|
-
# changetype
|
77
|
-
# dn
|
78
|
-
#
|
79
|
-
# TODO: Detect presence/location of changelog from root DSE
|
80
|
-
def each_change
|
81
|
-
with_ldap do |ldap|
|
82
|
-
log.debug "@last_change_number = #{@last_change_number}"
|
83
|
-
filter = "(changenumber>=#{@last_change_number})"
|
84
|
-
first = true
|
85
|
-
@full_refresh_required = false
|
86
|
-
ldap.search :base => changelog_dn, :filter =>filter do |change|
|
87
|
-
change_number = change.changenumber[0].to_i
|
88
|
-
if first
|
89
|
-
first = false
|
90
|
-
# TODO: Persist the change_number so that we don't do a full resync everytime rubysync starts
|
91
|
-
if change_number != @last_change_number
|
92
|
-
log.warn "Earliest change number (#{change_number}) differs from that recorded (#{@last_change_number})."
|
93
|
-
log.warn "A full refresh is required."
|
94
|
-
@full_refresh_required = true
|
95
|
-
break
|
96
|
-
end
|
97
|
-
else
|
98
|
-
@last_change_number = change_number if change_number > @last_change_number
|
99
|
-
# todo: A proper DN object would be nice instead of string manipulation
|
100
|
-
target_dn = change.targetdn[0].gsub(/\s*,\s*/,',')
|
101
|
-
if target_dn =~ /#{search_base}$/oi
|
102
|
-
change_type = change.changetype[0]
|
103
|
-
event = event_for_changelog_entry(change)
|
104
|
-
yield event
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
def skip_existing_changelog_entries
|
113
|
-
with_ldap do |ldap|
|
114
|
-
filter = "(changenumber>=#{@last_change_number})"
|
115
|
-
@full_refresh_required = false
|
116
|
-
ldap.search :base => changelog_dn, :filter =>filter do |change|
|
117
|
-
change_number = change.changenumber[0].to_i
|
118
|
-
@last_change_number = change_number if change_number > @last_change_number
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
60
|
|
123
61
|
def each_entry
|
124
62
|
Net::LDAP.open(:host=>host, :port=>port, :auth=>auth) do |ldap|
|
125
|
-
ldap.search :base => search_base, :filter => search_filter do |
|
126
|
-
|
127
|
-
association_key = (is_vault?)? nil : entry.dn
|
128
|
-
yield RubySync::Event.add(self, entry.dn, association_key, operations)
|
63
|
+
ldap.search :base => search_base, :filter => search_filter do |ldap_entry|
|
64
|
+
yield ldap_entry.dn, to_entry(ldap_entry)
|
129
65
|
end
|
130
66
|
end
|
131
67
|
end
|
@@ -137,10 +73,6 @@ module RubySync::Connectors
|
|
137
73
|
log.warn "Returning a likely sample set."
|
138
74
|
%w{ cn givenName sn }
|
139
75
|
end
|
140
|
-
|
141
|
-
|
142
|
-
def stopped
|
143
|
-
end
|
144
76
|
|
145
77
|
|
146
78
|
|
@@ -148,11 +80,11 @@ module RubySync::Connectors
|
|
148
80
|
return <<END
|
149
81
|
|
150
82
|
host 'localhost'
|
151
|
-
port
|
152
|
-
username '
|
83
|
+
port 389
|
84
|
+
username 'cn=Manager,dc=my-domain,dc=com'
|
153
85
|
password 'secret'
|
154
86
|
search_filter "cn=*"
|
155
|
-
search_base "dc=
|
87
|
+
search_base "ou=users,o=my-organization,dc=my-domain,dc=com"
|
156
88
|
#:bind_method :simple
|
157
89
|
END
|
158
90
|
end
|
@@ -160,12 +92,16 @@ END
|
|
160
92
|
|
161
93
|
|
162
94
|
def add(path, operations)
|
95
|
+
result = nil
|
163
96
|
with_ldap do |ldap|
|
164
|
-
|
97
|
+
attributes = perform_operations(operations)
|
98
|
+
result = ldap.add :dn=>path, :attributes=>attributes
|
165
99
|
end
|
100
|
+
log.debug("ldap.add returned '#{result}'")
|
166
101
|
return true
|
167
|
-
rescue
|
168
|
-
log.
|
102
|
+
rescue Exception
|
103
|
+
log.warn "Exception occurred while adding LDAP record"
|
104
|
+
log.debug $!
|
169
105
|
false
|
170
106
|
end
|
171
107
|
|
@@ -192,7 +128,7 @@ END
|
|
192
128
|
|
193
129
|
# Called by unit tests to inject data
|
194
130
|
def test_add id, details
|
195
|
-
details << RubySync::Operation.new(:add, "objectclass", ['inetOrgPerson'
|
131
|
+
details << RubySync::Operation.new(:add, "objectclass", ['inetOrgPerson'])
|
196
132
|
add id, details
|
197
133
|
end
|
198
134
|
|
@@ -201,111 +137,17 @@ END
|
|
201
137
|
#is_vault? and event.add_value 'objectclass', RUBYSYNC_ASSOCIATION_CLASS
|
202
138
|
end
|
203
139
|
|
204
|
-
def associate association, path
|
205
|
-
with_ldap do |ldap|
|
206
|
-
# todo: check and warn if path is outside of search_base
|
207
|
-
ldap.modify :dn=>path, :operations=>[
|
208
|
-
[:add, RUBYSYNC_ASSOCIATION_ATTRIBUTE, association.to_s]
|
209
|
-
]
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def path_for_association association
|
214
|
-
with_ldap do |ldap|
|
215
|
-
filter = "#{RUBYSYNC_ASSOCIATION_ATTRIBUTE}=#{association.to_s}"
|
216
|
-
log.debug "Searching with filter: #{filter}"
|
217
|
-
results = ldap.search :base=>@search_base,
|
218
|
-
:filter=>filter,
|
219
|
-
:attributes=>[]
|
220
|
-
results or return nil
|
221
|
-
case results.length
|
222
|
-
when 0: return nil
|
223
|
-
when 1: return results[0].dn
|
224
|
-
else
|
225
|
-
raise Exception.new("Duplicate association found for #{association.to_s}")
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def associations_for path
|
231
|
-
with_ldap do |ldap|
|
232
|
-
results = ldap.search :base=>path,
|
233
|
-
:scope=>Net::LDAP::SearchScope_BaseObject,
|
234
|
-
:attributes=>[RUBYSYNC_ASSOCIATION_ATTRIBUTE]
|
235
|
-
unless results and results.length > 0
|
236
|
-
log.warn "Attempted association lookup on non-existent LDAP entry '#{path}'"
|
237
|
-
return []
|
238
|
-
end
|
239
|
-
associations = results[0][RUBYSYNC_ASSOCIATION_ATTRIBUTE]
|
240
|
-
return (associations)? associations.as_array : []
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
def remove_association association
|
245
|
-
path = path_for_association association
|
246
|
-
with_ldap do |ldap|
|
247
|
-
ldap.modify :dn=>path, :modifications=>[
|
248
|
-
[:delete, RUBYSYNC_ASSOCIATION_ATTRIBUTE, association.to_s]
|
249
|
-
]
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
|
254
|
-
# def associate_with_foreign_key key, path
|
255
|
-
# with_ldap do |ldap|
|
256
|
-
# ldap.add_attribute(path, association_attribute, key.to_s)
|
257
|
-
# end
|
258
|
-
# end
|
259
|
-
#
|
260
|
-
# def path_for_foreign_key key
|
261
|
-
# entry = entry_for_foreign_key key
|
262
|
-
# (entry)? entry.dn : nil
|
263
|
-
# end
|
264
|
-
#
|
265
|
-
# def foreign_key_for path
|
266
|
-
# entry = self[path]
|
267
|
-
# (entry)? entry.dn : nil # TODO: That doesn't look right. Should return an association key, not a path.
|
268
|
-
# end
|
269
|
-
#
|
270
|
-
# def remove_foreign_key key
|
271
|
-
# with_ldap do |ldap|
|
272
|
-
# entry = entry_for_foreign_key key
|
273
|
-
# if entry
|
274
|
-
# modify :dn=>entry.dn, :operations=>[ [:delete, association_attribute, key] ]
|
275
|
-
# end
|
276
|
-
# end
|
277
|
-
# end
|
278
|
-
#
|
279
|
-
# def find_associated foreign_key
|
280
|
-
# entry = entry_for_foreign_key key
|
281
|
-
# (entry)? operations_for_entry(entry) : nil
|
282
|
-
# end
|
283
140
|
|
284
141
|
|
285
|
-
private
|
142
|
+
private
|
286
143
|
|
287
|
-
def
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
if cle.attribute_names.include? :changes
|
292
|
-
payload = []
|
293
|
-
cr = Net::LDIF.parse("dn: #{dn}\nchangetype: #{changetype}\n#{cle.changes[0]}")[0]
|
294
|
-
if changetype.to_sym == :add
|
295
|
-
# cr.data will be a hash of arrays or strings (attr-name=>[value1, value2, ...])
|
296
|
-
cr.data.each do |name, values|
|
297
|
-
payload << RubySync::Operation.add(name, values)
|
298
|
-
end
|
299
|
-
else
|
300
|
-
# cr.data will be an array of arrays of form [:action, :subject, [values]]
|
301
|
-
cr.data.each do |record|
|
302
|
-
payload << RubySync::Operation.new(record[0], record[1], record[2])
|
303
|
-
end
|
304
|
-
end
|
144
|
+
def to_entry ldap_entry
|
145
|
+
entry = {}
|
146
|
+
ldap_entry.each do |name, values|
|
147
|
+
entry[name.to_s] = values.map {|v| String.new(v)}
|
305
148
|
end
|
306
|
-
|
149
|
+
entry
|
307
150
|
end
|
308
|
-
|
309
151
|
|
310
152
|
def operations_for_entry entry
|
311
153
|
ops = []
|
@@ -315,13 +157,8 @@ private
|
|
315
157
|
ops
|
316
158
|
end
|
317
159
|
|
318
|
-
|
319
|
-
|
320
|
-
result = ldap.search :base=>search_base, :filter=>"#{association_attribute}=#{key}"
|
321
|
-
return nil if !result or result.size == 0
|
322
|
-
result[0]
|
323
|
-
end
|
324
|
-
end
|
160
|
+
|
161
|
+
|
325
162
|
|
326
163
|
|
327
164
|
def with_ldap
|