~ubuntu-branches/ubuntu/oneiric/puppet/oneiric-security

« back to all changes in this revision

Viewing changes to lib/puppet/util/ldap/manager.rb

  • Committer: Bazaar Package Importer
  • Author(s): Micah Anderson
  • Date: 2008-07-26 15:43:45 UTC
  • mto: (3.1.1 lenny) (1.3.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20080726154345-1fmgo76b4l72ulvc
ImportĀ upstreamĀ versionĀ 0.24.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'puppet/util/ldap'
 
2
require 'puppet/util/ldap/connection'
 
3
require 'puppet/util/ldap/generator'
 
4
 
 
5
# The configuration class for LDAP providers, plus
 
6
# connection handling for actually interacting with ldap.
 
7
class Puppet::Util::Ldap::Manager
 
8
    attr_reader :objectclasses, :puppet2ldap, :location, :rdn
 
9
 
 
10
    # A null-op that just returns the config.
 
11
    def and
 
12
        return self
 
13
    end
 
14
 
 
15
    # Set the offset from the search base and return the config.
 
16
    def at(location)
 
17
        @location = location
 
18
        return self
 
19
    end
 
20
 
 
21
    # The basic search base.
 
22
    def base
 
23
        [location, Puppet[:ldapbase]].join(",")
 
24
    end
 
25
 
 
26
    # Convert the name to a dn, then pass the args along to
 
27
    # our connection.
 
28
    def create(name, attributes)
 
29
        attributes = attributes.dup
 
30
 
 
31
        # Add the objectclasses
 
32
        attributes["objectClass"] = objectclasses.collect { |o| o.to_s }
 
33
        attributes["objectClass"] << "top" unless attributes["objectClass"].include?("top")
 
34
 
 
35
        attributes[rdn.to_s] = [name]
 
36
 
 
37
        # Generate any new values we might need.
 
38
        generate(attributes)
 
39
 
 
40
        # And create our resource.
 
41
        connect { |conn| conn.add dn(name), attributes }
 
42
    end
 
43
 
 
44
    # Open, yield, and close the connection.  Cannot be left
 
45
    # open, at this point.
 
46
    def connect
 
47
        raise ArgumentError, "You must pass a block to #connect" unless block_given?
 
48
 
 
49
        unless defined?(@connection) and @connection
 
50
            if Puppet[:ldaptls]
 
51
                ssl = :tls
 
52
            elsif Puppet[:ldapssl]
 
53
                ssl = true
 
54
            else
 
55
                ssl = false
 
56
            end
 
57
            options = {:ssl => ssl}
 
58
            if user = Puppet[:ldapuser] and user != ""
 
59
                options[:user] = user
 
60
            end
 
61
            if password = Puppet[:ldappassword] and password != ""
 
62
                options[:password] = password
 
63
            end
 
64
            @connection = Puppet::Util::Ldap::Connection.new(Puppet[:ldapserver], Puppet[:ldapport], options)
 
65
        end
 
66
        @connection.start
 
67
        begin
 
68
            yield @connection.connection
 
69
        ensure
 
70
            @connection.close
 
71
        end
 
72
        return nil
 
73
    end
 
74
 
 
75
    # Convert the name to a dn, then pass the args along to
 
76
    # our connection.
 
77
    def delete(name)
 
78
        connect { |connection| connection.delete dn(name) }
 
79
    end
 
80
 
 
81
    # Calculate the dn for a given resource.
 
82
    def dn(name)
 
83
        ["#{rdn.to_s}=%s" % name, base].join(",")
 
84
    end
 
85
 
 
86
    # Convert an ldap-style entry hash to a provider-style hash.
 
87
    def entry2provider(entry)
 
88
        raise ArgumentError, "Could not get dn from ldap entry" unless entry["dn"]
 
89
 
 
90
        # DN is always a single-entry array.  Strip off the bits before the
 
91
        # first comma, then the bits after the remaining equal sign.  This is the
 
92
        # name.
 
93
        name = entry["dn"].dup.pop.split(",").shift.split("=").pop
 
94
 
 
95
        result = {:name => name}
 
96
 
 
97
        @ldap2puppet.each do |ldap, puppet|
 
98
            result[puppet] = entry[ldap.to_s] || :absent
 
99
        end
 
100
 
 
101
        result
 
102
    end
 
103
 
 
104
    # Create our normal search filter.
 
105
    def filter
 
106
        return "objectclass=%s" % objectclasses[0] if objectclasses.length == 1
 
107
        return "(&(objectclass=" + objectclasses.join(")(objectclass=") + "))"
 
108
    end
 
109
 
 
110
    # Find the associated entry for a resource.  Returns a hash, minus
 
111
    # 'dn', or nil if the entry cannot be found.
 
112
    def find(name)
 
113
        result = nil
 
114
        connect do |conn|
 
115
            begin
 
116
                conn.search2(dn(name), 0, "objectclass=*") do |result|
 
117
                    # Convert to puppet-appropriate attributes
 
118
                    return entry2provider(result)
 
119
                end
 
120
            rescue => detail
 
121
                return nil
 
122
            end
 
123
        end
 
124
    end
 
125
 
 
126
    # Declare a new attribute generator.
 
127
    def generates(parameter)
 
128
        @generators << Puppet::Util::Ldap::Generator.new(parameter)
 
129
        @generators[-1]
 
130
    end
 
131
 
 
132
    # Generate any extra values we need to make the ldap entry work.
 
133
    def generate(values)
 
134
        return unless @generators.length > 0
 
135
 
 
136
        @generators.each do |generator|
 
137
            # Don't override any values that might exist.
 
138
            next if values[generator.name]
 
139
 
 
140
            if generator.source
 
141
                unless value = values[generator.source]
 
142
                    raise ArgumentError, "%s must be defined to generate %s" % [generator.source, generator.name]
 
143
                end
 
144
                result = generator.generate(value)
 
145
            else
 
146
                result = generator.generate
 
147
            end
 
148
 
 
149
            result = [result] unless result.is_a?(Array)
 
150
            result = result.collect { |r| r.to_s }
 
151
 
 
152
            values[generator.name] = result
 
153
        end
 
154
    end
 
155
 
 
156
    def initialize
 
157
        @rdn = :cn
 
158
        @generators = []
 
159
    end
 
160
 
 
161
    # Specify what classes this provider models.
 
162
    def manages(*classes)
 
163
        @objectclasses = classes
 
164
        return self
 
165
    end
 
166
 
 
167
    # Specify the attribute map.  Assumes the keys are the puppet
 
168
    # attributes, and the values are the ldap attributes, and creates a map
 
169
    # for each direction.
 
170
    def maps(attributes)
 
171
        # The map with the puppet attributes as the keys
 
172
        @puppet2ldap = attributes
 
173
 
 
174
        # and the ldap attributes as the keys.
 
175
        @ldap2puppet = attributes.inject({}) { |map, ary| map[ary[1]] = ary[0]; map }
 
176
 
 
177
        return self
 
178
    end
 
179
 
 
180
    # Return the ldap name for a puppet attribute.
 
181
    def ldap_name(attribute)
 
182
        @puppet2ldap[attribute].to_s
 
183
    end
 
184
 
 
185
    # Convert the name to a dn, then pass the args along to
 
186
    # our connection.
 
187
    def modify(name, mods)
 
188
        connect { |connection| connection.modify dn(name), mods }
 
189
    end
 
190
 
 
191
    # Specify the rdn that we use to build up our dn.
 
192
    def named_by(attribute)
 
193
        @rdn = attribute
 
194
        self
 
195
    end
 
196
 
 
197
    # Return the puppet name for an ldap attribute.
 
198
    def puppet_name(attribute)
 
199
        @ldap2puppet[attribute]
 
200
    end
 
201
 
 
202
    # Search for all entries at our base.  A potentially expensive search.
 
203
    def search(sfilter = nil)
 
204
        sfilter ||= filter()
 
205
 
 
206
        result = []
 
207
        connect do |conn|
 
208
            conn.search2(base, 1, sfilter) do |entry|
 
209
                result << entry2provider(entry)
 
210
            end
 
211
        end
 
212
        return nil if result.empty?
 
213
        return result
 
214
    end
 
215
 
 
216
    # Update the ldap entry with the desired state.
 
217
    def update(name, is, should)
 
218
        if should[:ensure] == :absent
 
219
            Puppet.info "Removing %s from ldap" % dn(name)
 
220
            delete(name)
 
221
            return
 
222
        end
 
223
 
 
224
        # We're creating a new entry
 
225
        if is.empty? or is[:ensure] == :absent
 
226
            Puppet.info "Creating %s in ldap" % dn(name)
 
227
            # Remove any :absent params and :ensure, then convert the names to ldap names.
 
228
            attrs = ldap_convert(should)
 
229
            create(name, attrs)
 
230
            return
 
231
        end
 
232
 
 
233
        # We're modifying an existing entry.  Yuck.
 
234
 
 
235
        mods = []
 
236
        # For each attribute we're deleting that is present, create a
 
237
        # modify instance for deletion.
 
238
        [is.keys, should.keys].flatten.uniq.each do |property|
 
239
            # They're equal, so do nothing.
 
240
            next if is[property] == should[property]
 
241
 
 
242
            attributes = ldap_convert(should)
 
243
 
 
244
            prop_name = ldap_name(property).to_s
 
245
 
 
246
            # We're creating it.
 
247
            if is[property] == :absent or is[property].nil?
 
248
                mods << LDAP::Mod.new(LDAP::LDAP_MOD_ADD, prop_name, attributes[prop_name])
 
249
                next
 
250
            end
 
251
 
 
252
            # We're deleting it
 
253
            if should[property] == :absent or should[property].nil?
 
254
                mods << LDAP::Mod.new(LDAP::LDAP_MOD_DELETE, prop_name, [])
 
255
                next
 
256
            end
 
257
 
 
258
            # We're replacing an existing value
 
259
            mods << LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE, prop_name, attributes[prop_name])
 
260
        end
 
261
 
 
262
        modify(name, mods)
 
263
    end
 
264
 
 
265
    # Is this a complete ldap configuration?
 
266
    def valid?
 
267
        location and objectclasses and ! objectclasses.empty? and puppet2ldap
 
268
    end
 
269
 
 
270
    private
 
271
 
 
272
    # Convert a hash of attributes to ldap-like forms.  This mostly means
 
273
    # getting rid of :ensure and making sure everything's an array of strings.
 
274
    def ldap_convert(attributes)
 
275
        attributes.reject { |param, value| value == :absent or param == :ensure }.inject({}) do |result, ary|
 
276
            value = (ary[1].is_a?(Array) ? ary[1] : [ary[1]]).collect { |v| v.to_s }
 
277
            result[ldap_name(ary[0])] = value
 
278
            result
 
279
        end
 
280
    end
 
281
end