ruby-net-ldap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+