net-ldap 0.0.5

Sign up to get free protection for your applications and to get access to all the features.

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
+