ruby-net-ldap 0.0.1

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,155 @@
1
+ # $Id: pdu.rb 85 2006-04-30 16:31:08Z blackhedd $
2
+ #
3
+ # LDAP PDU support classes
4
+ #
5
+ #
6
+ #----------------------------------------------------------------------------
7
+ #
8
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
9
+ #
10
+ # Gmail: garbagecat10
11
+ #
12
+ # This program is free software; you can redistribute it and/or modify
13
+ # it under the terms of the GNU General Public License as published by
14
+ # the Free Software Foundation; either version 2 of the License, or
15
+ # (at your option) any later version.
16
+ #
17
+ # This program is distributed in the hope that it will be useful,
18
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ # GNU General Public License for more details.
21
+ #
22
+ # You should have received a copy of the GNU General Public License
23
+ # along with this program; if not, write to the Free Software
24
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25
+ #
26
+ #---------------------------------------------------------------------------
27
+ #
28
+
29
+
30
+
31
+ module Net
32
+
33
+
34
+ class LdapPduError < Exception; end
35
+
36
+
37
+ class LdapPdu
38
+
39
+ BindResult = 1
40
+ SearchReturnedData = 4
41
+ SearchResult = 5
42
+ ModifyResponse = 7
43
+ AddResponse = 9
44
+ DeleteResponse = 11
45
+ ModifyRDNResponse = 13
46
+
47
+ attr_reader :msg_id, :app_tag
48
+ attr_reader :search_dn, :search_attributes, :search_entry
49
+
50
+ #
51
+ # initialize
52
+ # An LDAP PDU always looks like a BerSequence with
53
+ # two elements: an integer (message-id number), and
54
+ # an application-specific sequence.
55
+ # The application-specific tag in the sequence tells
56
+ # us what kind of packet it is, and each kind has its
57
+ # own format, defined in RFC-1777.
58
+ # Observe that many clients (such as ldapsearch)
59
+ # do not necessarily enforce the expected application
60
+ # tags on received protocol packets. This implementation
61
+ # does interpret the RFC strictly in this regard, and
62
+ # it remains to be seen whether there are servers out
63
+ # there that will not work well with our approach.
64
+ #
65
+ def initialize ber_object
66
+ begin
67
+ @msg_id = ber_object[0].to_i
68
+ @app_tag = ber_object[1].ber_identifier - 0x60
69
+ rescue
70
+ # any error becomes a data-format error
71
+ raise LdapPduError.new( "ldap-pdu format error" )
72
+ end
73
+
74
+ case @app_tag
75
+ when BindResult
76
+ parse_ldap_result ber_object[1]
77
+ when SearchReturnedData
78
+ parse_search_return ber_object[1]
79
+ when SearchResult
80
+ parse_ldap_result ber_object[1]
81
+ when ModifyResponse
82
+ parse_ldap_result ber_object[1]
83
+ when AddResponse
84
+ parse_ldap_result ber_object[1]
85
+ when DeleteResponse
86
+ parse_ldap_result ber_object[1]
87
+ when ModifyRDNResponse
88
+ parse_ldap_result ber_object[1]
89
+ else
90
+ raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" )
91
+ end
92
+ end
93
+
94
+ #
95
+ # result_code
96
+ # This returns an LDAP result code taken from the PDU,
97
+ # but it will be nil if there wasn't a result code.
98
+ # That can easily happen depending on the type of packet.
99
+ #
100
+ def result_code code = :resultCode
101
+ @ldap_result and @ldap_result[code]
102
+ end
103
+
104
+
105
+ private
106
+
107
+ #
108
+ # parse_ldap_result
109
+ #
110
+ def parse_ldap_result sequence
111
+ sequence.length >= 3 or raise LdapPduError
112
+ @ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
113
+ end
114
+
115
+ #
116
+ # parse_search_return
117
+ # Definition from RFC 1777 (we're handling application-4 here)
118
+ #
119
+ # Search Response ::=
120
+ # CHOICE {
121
+ # entry [APPLICATION 4] SEQUENCE {
122
+ # objectName LDAPDN,
123
+ # attributes SEQUENCE OF SEQUENCE {
124
+ # AttributeType,
125
+ # SET OF AttributeValue
126
+ # }
127
+ # },
128
+ # resultCode [APPLICATION 5] LDAPResult
129
+ # }
130
+ #
131
+ # We concoct a search response that is a hash of the returned attribute values.
132
+ # NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
133
+ # This is to make them more predictable for user programs, but it
134
+ # may not be a good idea. Maybe this should be configurable.
135
+ # ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes,
136
+ # we also return @search_entry, which is an LDAP::Entry object.
137
+ # If that works out well, then we'll remove the first two.
138
+ #
139
+ def parse_search_return sequence
140
+ sequence.length >= 2 or raise LdapPduError
141
+ @search_entry = LDAP::Entry.new( sequence[0] )
142
+ @search_dn = sequence[0]
143
+ @search_attributes = {}
144
+ sequence[1].each {|seq|
145
+ @search_entry[seq[0]] = seq[1]
146
+ @search_attributes[seq[0].downcase.intern] = seq[1]
147
+ }
148
+ end
149
+
150
+
151
+ end
152
+
153
+
154
+ end # module Net
155
+
@@ -0,0 +1,64 @@
1
+ # $Id: psw.rb 73 2006-04-24 21:59:35Z blackhedd $
2
+ #
3
+ #
4
+ #----------------------------------------------------------------------------
5
+ #
6
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
7
+ #
8
+ # Gmail: garbagecat10
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
+ #
24
+ #---------------------------------------------------------------------------
25
+ #
26
+ #
27
+
28
+
29
+ module Net
30
+ class LDAP
31
+
32
+
33
+ class Password
34
+ class << self
35
+
36
+ # Generate a password-hash suitable for inclusion in an LDAP attribute.
37
+ # Pass a hash type (currently supported: :md5 and :sha) and a plaintext
38
+ # password. This function will return a hashed representation.
39
+ # STUB: This is here to fulfill the requirements of an RFC, which one?
40
+ # TODO, gotta do salted-sha and (maybe) salted-md5.
41
+ # Should we provide sha1 as a synonym for sha1? I vote no because then
42
+ # should you also provide ssha1 for symmetry?
43
+ def generate( type, str )
44
+ case type
45
+ when :md5
46
+ require 'md5'
47
+ "{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }"
48
+ when :sha
49
+ require 'sha1'
50
+ "{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }"
51
+ # when ssha
52
+ else
53
+ raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" )
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+
61
+ end # class LDAP
62
+ end # module Net
63
+
64
+
@@ -0,0 +1,39 @@
1
+ # $Id: ldif.rb 78 2006-04-26 02:57:34Z blackhedd $
2
+ #
3
+ # Net::LDIF for Ruby
4
+ #
5
+ #
6
+ #
7
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
8
+ #
9
+ # Gmail: garbagecat10
10
+ #
11
+ # This program is free software; you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation; either version 2 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU General Public License
22
+ # along with this program; if not, write to the Free Software
23
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24
+ #
25
+ #
26
+
27
+ # THIS FILE IS A STUB.
28
+
29
+ module Net
30
+
31
+ class LDIF
32
+
33
+
34
+ end # class LDIF
35
+
36
+
37
+ end # module Net
38
+
39
+
@@ -0,0 +1,42 @@
1
+ # $Id: testber.rb 57 2006-04-18 00:18:48Z blackhedd $
2
+ #
3
+ #
4
+
5
+
6
+ $:.unshift "lib"
7
+
8
+ require 'net/ldap'
9
+ require 'stringio'
10
+
11
+
12
+ class TestBer < Test::Unit::TestCase
13
+
14
+ def setup
15
+ end
16
+
17
+ # TODO: Add some much bigger numbers
18
+ # 5000000000 is a Bignum, which hits different code.
19
+ def test_ber_integers
20
+ assert_equal( "\002\001\005", 5.to_ber )
21
+ assert_equal( "\002\002\203t", 500.to_ber )
22
+ assert_equal( "\002\003\203\206P", 50000.to_ber )
23
+ assert_equal( "\002\005\222\320\227\344\000", 5000000000.to_ber )
24
+ end
25
+
26
+ def test_ber_parsing
27
+ assert_equal( 6, "\002\001\006".read_ber( Net::LDAP::AsnSyntax ))
28
+ assert_equal( "testing", "\004\007testing".read_ber( Net::LDAP::AsnSyntax ))
29
+ end
30
+
31
+
32
+ def test_ber_parser_on_ldap_bind_request
33
+ s = StringIO.new "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus"
34
+ assert_equal( [1, [3, "Administrator", "ad_is_bogus"]], s.read_ber( Net::LDAP::AsnSyntax ))
35
+ end
36
+
37
+
38
+
39
+
40
+ end
41
+
42
+
@@ -0,0 +1,101 @@
1
+ # $Id: testdata.ldif 50 2006-04-17 17:57:33Z blackhedd $
2
+ #
3
+ # This is test-data for an LDAP server in LDIF format.
4
+ #
5
+ dn: dc=bayshorenetworks,dc=com
6
+ objectClass: dcObject
7
+ objectClass: organization
8
+ o: Bayshore Networks LLC
9
+ dc: bayshorenetworks
10
+
11
+ dn: cn=Manager,dc=bayshorenetworks,dc=com
12
+ objectClass: organizationalrole
13
+ cn: Manager
14
+
15
+ dn: ou=people,dc=bayshorenetworks,dc=com
16
+ objectClass: organizationalunit
17
+ ou: people
18
+
19
+ dn: ou=privileges,dc=bayshorenetworks,dc=com
20
+ objectClass: organizationalunit
21
+ ou: privileges
22
+
23
+ dn: ou=roles,dc=bayshorenetworks,dc=com
24
+ objectClass: organizationalunit
25
+ ou: roles
26
+
27
+ dn: ou=office,dc=bayshorenetworks,dc=com
28
+ objectClass: organizationalunit
29
+ ou: office
30
+
31
+ dn: mail=nogoodnik@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
32
+ cn: Bob Fosse
33
+ mail: nogoodnik@steamheat.net
34
+ sn: Fosse
35
+ ou: people
36
+ objectClass: top
37
+ objectClass: inetorgperson
38
+ objectClass: authorizedperson
39
+ hasAccessRole: uniqueIdentifier=engineer,ou=roles
40
+ hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles
41
+ hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
42
+ hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
43
+ hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles
44
+ hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles
45
+ hasAccessRole: uniqueIdentifier=brandplace_logging_user,ou=roles
46
+ hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles
47
+ hasAccessRole: uniqueIdentifier=workorder_user,ou=roles
48
+ hasAccessRole: uniqueIdentifier=bayshore_eagle_user,ou=roles
49
+ hasAccessRole: uniqueIdentifier=bayshore_eagle_superuser,ou=roles
50
+ hasAccessRole: uniqueIdentifier=kledaras_user,ou=roles
51
+
52
+ dn: mail=elephant@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
53
+ cn: Gwen Verdon
54
+ mail: elephant@steamheat.net
55
+ sn: Verdon
56
+ ou: people
57
+ objectClass: top
58
+ objectClass: inetorgperson
59
+ objectClass: authorizedperson
60
+ hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles
61
+ hasAccessRole: uniqueIdentifier=engineer,ou=roles
62
+ hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
63
+ hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
64
+ hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles
65
+
66
+ dn: uniqueIdentifier=engineering,ou=privileges,dc=bayshorenetworks,dc=com
67
+ uniqueIdentifier: engineering
68
+ ou: privileges
69
+ objectClass: accessPrivilege
70
+
71
+ dn: uniqueIdentifier=engineer,ou=roles,dc=bayshorenetworks,dc=com
72
+ uniqueIdentifier: engineer
73
+ ou: roles
74
+ objectClass: accessRole
75
+ hasAccessPrivilege: uniqueIdentifier=engineering,ou=privileges
76
+
77
+ dn: uniqueIdentifier=ldapadmin,ou=roles,dc=bayshorenetworks,dc=com
78
+ uniqueIdentifier: ldapadmin
79
+ ou: roles
80
+ objectClass: accessRole
81
+
82
+ dn: uniqueIdentifier=ldapsuperadmin,ou=roles,dc=bayshorenetworks,dc=com
83
+ uniqueIdentifier: ldapsuperadmin
84
+ ou: roles
85
+ objectClass: accessRole
86
+
87
+ dn: mail=catperson@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
88
+ cn: Sid Sorokin
89
+ mail: catperson@steamheat.net
90
+ sn: Sorokin
91
+ ou: people
92
+ objectClass: top
93
+ objectClass: inetorgperson
94
+ objectClass: authorizedperson
95
+ hasAccessRole: uniqueIdentifier=engineer,ou=roles
96
+ hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
97
+ hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
98
+ hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles
99
+ hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles
100
+ hasAccessRole: uniqueIdentifier=workorder_user,ou=roles
101
+
@@ -0,0 +1,11 @@
1
+ # $Id: testem.rb 72 2006-04-24 21:58:14Z blackhedd $
2
+ #
3
+ #
4
+
5
+ require 'test/unit'
6
+ require 'tests/testber'
7
+ require 'tests/testldif'
8
+ require 'tests/testldap'
9
+ require 'tests/testpsw'
10
+
11
+
@@ -0,0 +1,190 @@
1
+ # $Id: testldap.rb 65 2006-04-23 01:17:49Z blackhedd $
2
+ #
3
+ #
4
+
5
+
6
+ $:.unshift "lib"
7
+
8
+ require 'test/unit'
9
+
10
+ require 'net/ldap'
11
+ require 'stringio'
12
+
13
+
14
+ class TestLdapClient < Test::Unit::TestCase
15
+
16
+ # TODO: these tests crash and burn if the associated
17
+ # LDAP testserver isn't up and running.
18
+ # We rely on being able to read a file with test data
19
+ # in LDIF format.
20
+ # TODO, WARNING: for the moment, this data is in a file
21
+ # whose name and location are HARDCODED into the
22
+ # instance method load_test_data.
23
+
24
+ def setup
25
+ @host = "127.0.0.1"
26
+ @port = 3890
27
+ @auth = {
28
+ :method => :simple,
29
+ :username => "cn=bigshot,dc=bayshorenetworks,dc=com",
30
+ :password => "opensesame"
31
+ }
32
+
33
+ @ldif = load_test_data
34
+ end
35
+
36
+
37
+
38
+ # Get some test data which will be used to validate
39
+ # the responses from the test LDAP server we will
40
+ # connect to.
41
+ # TODO, Bogus: we are HARDCODING the location of the file for now.
42
+ #
43
+ def load_test_data
44
+ ary = File.readlines( "tests/testdata.ldif" )
45
+ hash = {}
46
+ while line = ary.shift and line.chomp!
47
+ if line =~ /^dn:[\s]*/i
48
+ dn = $'
49
+ hash[dn] = {}
50
+ while attr = ary.shift and attr.chomp! and attr =~ /^([\w]+)[\s]*:[\s]*/
51
+ hash[dn][$1.downcase.intern] ||= []
52
+ hash[dn][$1.downcase.intern] << $'
53
+ end
54
+ end
55
+ end
56
+ hash
57
+ end
58
+
59
+
60
+
61
+ # Binding tests.
62
+ # Need tests for all kinds of network failures and incorrect auth.
63
+ # TODO: Implement a class-level timeout for operations like bind.
64
+ # Search has a timeout defined at the protocol level, other ops do not.
65
+ # TODO, use constants for the LDAP result codes, rather than hardcoding them.
66
+ def test_bind
67
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
68
+ assert_equal( true, ldap.bind )
69
+ assert_equal( 0, ldap.get_operation_result.code )
70
+ assert_equal( "Success", ldap.get_operation_result.message )
71
+
72
+ bad_username = @auth.merge( {:username => "cn=badguy,dc=imposters,dc=com"} )
73
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_username
74
+ assert_equal( false, ldap.bind )
75
+ assert_equal( 48, ldap.get_operation_result.code )
76
+ assert_equal( "Inappropriate Authentication", ldap.get_operation_result.message )
77
+
78
+ bad_password = @auth.merge( {:password => "cornhusk"} )
79
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_password
80
+ assert_equal( false, ldap.bind )
81
+ assert_equal( 49, ldap.get_operation_result.code )
82
+ assert_equal( "Invalid Credentials", ldap.get_operation_result.message )
83
+ end
84
+
85
+
86
+
87
+ def test_search
88
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
89
+
90
+ search = {:base => "dc=smalldomain,dc=com"}
91
+ assert_equal( false, ldap.search( search ))
92
+ assert_equal( 32, ldap.get_operation_result.code )
93
+
94
+ search = {:base => "dc=bayshorenetworks,dc=com"}
95
+ assert_equal( true, ldap.search( search ))
96
+ assert_equal( 0, ldap.get_operation_result.code )
97
+
98
+ ldap.search( search ) {|res|
99
+ assert_equal( res, @ldif )
100
+ }
101
+ end
102
+
103
+
104
+
105
+
106
+ # This is a helper routine for test_search_attributes.
107
+ def internal_test_search_attributes attrs_to_search
108
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
109
+ assert( ldap.bind )
110
+
111
+ search = {
112
+ :base => "dc=bayshorenetworks,dc=com",
113
+ :attributes => attrs_to_search
114
+ }
115
+
116
+ ldif = @ldif
117
+ ldif.each {|dn,entry|
118
+ entry.delete_if {|attr,value|
119
+ ! attrs_to_search.include?(attr)
120
+ }
121
+ }
122
+
123
+ assert_equal( true, ldap.search( search ))
124
+ ldap.search( search ) {|res|
125
+ res_keys = res.keys.sort
126
+ ldif_keys = ldif.keys.sort
127
+ assert( res_keys, ldif_keys )
128
+ res.keys.each {|rk|
129
+ assert( res[rk], ldif[rk] )
130
+ }
131
+ }
132
+ end
133
+
134
+
135
+ def test_search_attributes
136
+ internal_test_search_attributes [:mail]
137
+ internal_test_search_attributes [:cn]
138
+ internal_test_search_attributes [:ou]
139
+ internal_test_search_attributes [:hasaccessprivilege]
140
+ internal_test_search_attributes ["mail"]
141
+ internal_test_search_attributes ["cn"]
142
+ internal_test_search_attributes ["ou"]
143
+ internal_test_search_attributes ["hasaccessrole"]
144
+
145
+ internal_test_search_attributes [:mail, :cn, :ou, :hasaccessrole]
146
+ internal_test_search_attributes [:mail, "cn", :ou, "hasaccessrole"]
147
+ end
148
+
149
+
150
+ def test_search_filters
151
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
152
+ search = {
153
+ :base => "dc=bayshorenetworks,dc=com",
154
+ :filter => Net::LDAP::Filter.eq( "sn", "Fosse" )
155
+ }
156
+
157
+ ldap.search( search ) {|res|
158
+ p res
159
+ }
160
+ end
161
+
162
+
163
+
164
+ def test_open
165
+ ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
166
+ ldap.open {|ldap|
167
+ 10.times {
168
+ rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
169
+ assert_equal( true, rc )
170
+ }
171
+ }
172
+ end
173
+
174
+
175
+ def test_ldap_open
176
+ Net::LDAP.open( :host => @host, :port => @port, :auth => @auth ) {|ldap|
177
+ 10.times {
178
+ rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
179
+ assert_equal( true, rc )
180
+ }
181
+ }
182
+ end
183
+
184
+
185
+
186
+
187
+
188
+ end
189
+
190
+