net-ldap 0.0.5

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.

Potentially problematic release.


This version of net-ldap might be problematic. Click here for more details.

@@ -0,0 +1,6 @@
1
+ Most of the tests here have been migrated to ../test
2
+ where all new tests will be created. These have been
3
+ left here for now just for reference and will me
4
+ migrated as well.
5
+
6
+ These will not be run automatically by rake.
@@ -0,0 +1,190 @@
1
+ # $Id$
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
+
@@ -0,0 +1,229 @@
1
+ # $Id$
2
+ #
3
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
4
+ # Gmail account: garbagecat10.
5
+ #
6
+ # This is an LDAP server intended for unit testing of Net::LDAP.
7
+ # It implements as much of the protocol as we have the stomach
8
+ # to implement but serves static data. Use ldapsearch to test
9
+ # this server!
10
+ #
11
+ # To make this easier to write, we use the Ruby/EventMachine
12
+ # reactor library.
13
+ #
14
+
15
+
16
+ require 'stringio'
17
+
18
+ #------------------------------------------------
19
+
20
+ class String
21
+ def read_ber! syntax=nil
22
+ s = StringIO.new self
23
+ pdu = s.read_ber(syntax)
24
+ if pdu
25
+ if s.eof?
26
+ slice!(0, length)
27
+ else
28
+ slice!(0, length - s.read.length)
29
+ end
30
+ end
31
+ pdu
32
+ end
33
+ end
34
+
35
+
36
+ module LdapServer
37
+
38
+ LdapServerAsnSyntax = {
39
+ :application => {
40
+ :constructed => {
41
+ 0 => :array, # LDAP BindRequest
42
+ 3 => :array # LDAP SearchRequest
43
+ },
44
+ :primitive => {
45
+ 2 => :string, # ldapsearch sends this to unbind
46
+ }
47
+ },
48
+ :context_specific => {
49
+ :primitive => {
50
+ 0 => :string, # simple auth (password)
51
+ 7 => :string # present filter
52
+ },
53
+ :constructed => {
54
+ 3 => :array # equality filter
55
+ },
56
+ }
57
+ }
58
+
59
+ def post_init
60
+ $logger.info "Accepted LDAP connection"
61
+ @authenticated = false
62
+ end
63
+
64
+ def receive_data data
65
+ @data ||= ""; @data << data
66
+ while pdu = @data.read_ber!(LdapServerAsnSyntax)
67
+ begin
68
+ handle_ldap_pdu pdu
69
+ rescue
70
+ $logger.error "closing connection due to error #{$!}"
71
+ close_connection
72
+ end
73
+ end
74
+ end
75
+
76
+ def handle_ldap_pdu pdu
77
+ tag_id = pdu[1].ber_identifier
78
+ case tag_id
79
+ when 0x60
80
+ handle_bind_request pdu
81
+ when 0x63
82
+ handle_search_request pdu
83
+ when 0x42
84
+ # bizarre thing, it's a null object (primitive application-2)
85
+ # sent by ldapsearch to request an unbind (or a kiss-off, not sure which)
86
+ close_connection_after_writing
87
+ else
88
+ $logger.error "received unknown packet-type #{tag_id}"
89
+ close_connection_after_writing
90
+ end
91
+ end
92
+
93
+ def handle_bind_request pdu
94
+ # TODO, return a proper LDAP error instead of blowing up on version error
95
+ if pdu[1][0] != 3
96
+ send_ldap_response 1, pdu[0].to_i, 2, "", "We only support version 3"
97
+ elsif pdu[1][1] != "cn=bigshot,dc=bayshorenetworks,dc=com"
98
+ send_ldap_response 1, pdu[0].to_i, 48, "", "Who are you?"
99
+ elsif pdu[1][2].ber_identifier != 0x80
100
+ send_ldap_response 1, pdu[0].to_i, 7, "", "Keep it simple, man"
101
+ elsif pdu[1][2] != "opensesame"
102
+ send_ldap_response 1, pdu[0].to_i, 49, "", "Make my day"
103
+ else
104
+ @authenticated = true
105
+ send_ldap_response 1, pdu[0].to_i, 0, pdu[1][1], "I'll take it"
106
+ end
107
+ end
108
+
109
+
110
+
111
+ #--
112
+ # Search Response ::=
113
+ # CHOICE {
114
+ # entry [APPLICATION 4] SEQUENCE {
115
+ # objectName LDAPDN,
116
+ # attributes SEQUENCE OF SEQUENCE {
117
+ # AttributeType,
118
+ # SET OF AttributeValue
119
+ # }
120
+ # },
121
+ # resultCode [APPLICATION 5] LDAPResult
122
+ # }
123
+ def handle_search_request pdu
124
+ unless @authenticated
125
+ # NOTE, early exit.
126
+ send_ldap_response 5, pdu[0].to_i, 50, "", "Who did you say you were?"
127
+ return
128
+ end
129
+
130
+ treebase = pdu[1][0]
131
+ if treebase != "dc=bayshorenetworks,dc=com"
132
+ send_ldap_response 5, pdu[0].to_i, 32, "", "unknown treebase"
133
+ return
134
+ end
135
+
136
+ msgid = pdu[0].to_i.to_ber
137
+
138
+ # pdu[1][7] is the list of requested attributes.
139
+ # If it's an empty array, that means that *all* attributes were requested.
140
+ requested_attrs = if pdu[1][7].length > 0
141
+ pdu[1][7].map {|a| a.downcase}
142
+ else
143
+ :all
144
+ end
145
+
146
+ filters = pdu[1][6]
147
+ if filters.length == 0
148
+ # NOTE, early exit.
149
+ send_ldap_response 5, pdu[0].to_i, 53, "", "No filter specified"
150
+ end
151
+
152
+ # TODO, what if this returns nil?
153
+ filter = Net::LDAP::Filter.parse_ldap_filter( filters )
154
+
155
+ $ldif.each {|dn, entry|
156
+ if filter.match( entry )
157
+ attrs = []
158
+ entry.each {|k, v|
159
+ if requested_attrs == :all or requested_attrs.include?(k.downcase)
160
+ attrvals = v.map {|v1| v1.to_ber}.to_ber_set
161
+ attrs << [k.to_ber, attrvals].to_ber_sequence
162
+ end
163
+ }
164
+
165
+ appseq = [dn.to_ber, attrs.to_ber_sequence].to_ber_appsequence(4)
166
+ pkt = [msgid.to_ber, appseq].to_ber_sequence
167
+ send_data pkt
168
+ end
169
+ }
170
+
171
+
172
+ send_ldap_response 5, pdu[0].to_i, 0, "", "Was that what you wanted?"
173
+ end
174
+
175
+
176
+
177
+ def send_ldap_response pkt_tag, msgid, code, dn, text
178
+ send_data( [msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag) ].to_ber )
179
+ end
180
+
181
+ end
182
+
183
+
184
+ #------------------------------------------------
185
+
186
+ # Rather bogus, a global method, which reads a HARDCODED filename
187
+ # parses out LDIF data. It will be used to serve LDAP queries out of this server.
188
+ #
189
+ def load_test_data
190
+ ary = File.readlines( "./testdata.ldif" )
191
+ hash = {}
192
+ while line = ary.shift and line.chomp!
193
+ if line =~ /^dn:[\s]*/i
194
+ dn = $'
195
+ hash[dn] = {}
196
+ while attr = ary.shift and attr.chomp! and attr =~ /^([\w]+)[\s]*:[\s]*/
197
+ hash[dn][$1.downcase] ||= []
198
+ hash[dn][$1.downcase] << $'
199
+ end
200
+ end
201
+ end
202
+ hash
203
+ end
204
+
205
+
206
+ #------------------------------------------------
207
+
208
+ if __FILE__ == $0
209
+
210
+ require 'rubygems'
211
+ require 'eventmachine'
212
+
213
+ require 'logger'
214
+ $logger = Logger.new $stderr
215
+
216
+ $logger.info "adding ../lib to loadpath, to pick up dev version of Net::LDAP."
217
+ $:.unshift "../lib"
218
+
219
+ $ldif = load_test_data
220
+
221
+ require 'net/ldap'
222
+
223
+ EventMachine.run {
224
+ $logger.info "starting LDAP server on 127.0.0.1 port 3890"
225
+ EventMachine.start_server "127.0.0.1", 3890, LdapServer
226
+ EventMachine.add_periodic_timer 60, proc {$logger.info "heartbeat"}
227
+ }
228
+ end
229
+
@@ -0,0 +1,101 @@
1
+ # $Id$
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
+