drbservice 1.0.4
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.
- data.tar.gz.sig +2 -0
- data/.gemtest +0 -0
- data/ChangeLog +249 -0
- data/History.rdoc +4 -0
- data/LICENSE +27 -0
- data/Manifest.txt +18 -0
- data/README.rdoc +74 -0
- data/Rakefile +38 -0
- data/examples/homedirservice.rb +110 -0
- data/examples/rubyversion.rb +26 -0
- data/lib/drb/authsslprotocol.rb +55 -0
- data/lib/drbservice.rb +208 -0
- data/lib/drbservice/ldapauth.rb +200 -0
- data/lib/drbservice/passwordauth.rb +58 -0
- data/lib/drbservice/utils.rb +426 -0
- data/spec/drb/authsslprotocol_spec.rb +76 -0
- data/spec/drbservice/ldapauth_spec.rb +382 -0
- data/spec/drbservice/passwordauth_spec.rb +141 -0
- data/spec/drbservice_spec.rb +168 -0
- data/spec/lib/helpers.rb +108 -0
- metadata +166 -0
- metadata.gz.sig +2 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/spec
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
basedir = Pathname( __FILE__ ).dirname.parent.parent
|
7
|
+
libdir = basedir + 'lib'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
|
10
|
+
$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
|
11
|
+
}
|
12
|
+
|
13
|
+
require 'rspec'
|
14
|
+
require 'spec/lib/helpers'
|
15
|
+
require 'drb/authsslprotocol'
|
16
|
+
|
17
|
+
|
18
|
+
describe DRb::DRbAuthenticatedSSLSocket do
|
19
|
+
|
20
|
+
before( :all ) do
|
21
|
+
setup_logging( :fatal )
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "URI parsing method" do
|
25
|
+
it "parses a valid drb authenticated SSL URI string into an Array of [host, port, option]" do
|
26
|
+
DRb::DRbAuthenticatedSSLSocket.parse_uri( VALID_SERVICE_URISTRING ).should ==
|
27
|
+
[ VALID_SERVICE_URI.host, VALID_SERVICE_URI.port, VALID_SERVICE_URI.query ]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "parses a valid drb authenticated SSL URI object into an Array of [host, port, option]" do
|
31
|
+
DRb::DRbAuthenticatedSSLSocket.parse_uri( VALID_SERVICE_URI ).should ==
|
32
|
+
[ VALID_SERVICE_URI.host, VALID_SERVICE_URI.port, VALID_SERVICE_URI.query ]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "parses the 'query' part of the URI as the 'option' return value" do
|
36
|
+
DRb::DRbAuthenticatedSSLSocket.parse_uri( VALID_SERVICE_URISTRING + '?an_option' ).
|
37
|
+
should == [ VALID_SERVICE_URI.host, VALID_SERVICE_URI.port, 'an_option' ]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises an exception if the URI scheme to be parsed isn't supported by this protocol" do
|
41
|
+
expect {
|
42
|
+
DRb::DRbAuthenticatedSSLSocket.parse_uri( 'drb://localhost:1718' )
|
43
|
+
}.to raise_exception( DRb::DRbBadScheme, /not a drbauthssl/i )
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises an exception if the port isn't specified by the URI to be parsed" do
|
47
|
+
expect {
|
48
|
+
DRb::DRbAuthenticatedSSLSocket.parse_uri( 'drbauthssl://localhost' )
|
49
|
+
}.to raise_exception( DRb::DRbBadURI, /missing the port/i )
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# [open(uri, config)] Open a client connection to the server at +uri+,
|
56
|
+
# using configuration +config+. Return a protocol
|
57
|
+
# instance for this connection.
|
58
|
+
describe "client-open method" do
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# [open_server(uri, config)] Open a server listening at +uri+,
|
63
|
+
# using configuration +config+. Return a
|
64
|
+
# protocol instance for this listener.
|
65
|
+
describe "server-open method" do
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# [uri_option(uri, config)] Take a URI, possibly containing an option
|
70
|
+
# component (e.g. a trailing '?param=val'),
|
71
|
+
# and return a [uri, option] tuple.
|
72
|
+
describe "client-open method" do
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
#!/usr/bin/spec
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
basedir = Pathname( __FILE__ ).dirname.parent.parent
|
7
|
+
libdir = basedir + 'lib'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
|
10
|
+
$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
|
11
|
+
}
|
12
|
+
|
13
|
+
require 'treequel'
|
14
|
+
|
15
|
+
require 'rspec'
|
16
|
+
require 'spec/lib/helpers'
|
17
|
+
|
18
|
+
require 'drbservice'
|
19
|
+
require 'drbservice/ldapauth'
|
20
|
+
|
21
|
+
|
22
|
+
describe DRbService::LDAPAuthentication do
|
23
|
+
include DRbService::SpecHelpers
|
24
|
+
|
25
|
+
TEST_URI = 'ldap://ldap.acme.com/dc=acme,dc=com'
|
26
|
+
TEST_DN_PATTERN = 'uid=%s,ou=people,dc=acme,dc=com'
|
27
|
+
TEST_FILTER_PATTERN = '(&(uid=%s)(objectClass=posixAccount))'
|
28
|
+
TEST_BASE = 'ou=employees,dc=acme,dc=com'
|
29
|
+
|
30
|
+
|
31
|
+
before( :all ) do
|
32
|
+
setup_logging( :fatal )
|
33
|
+
end
|
34
|
+
|
35
|
+
after( :all ) do
|
36
|
+
reset_logging()
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
describe "mixed into a DRbService" do
|
41
|
+
|
42
|
+
it "provides a declarative to set the LDAP URI for the service" do
|
43
|
+
serviceclass = Class.new( DRbService ) do
|
44
|
+
include DRbService::LDAPAuthentication
|
45
|
+
ldap_uri TEST_URI
|
46
|
+
end
|
47
|
+
serviceclass.ldap_uri.should == TEST_URI
|
48
|
+
end
|
49
|
+
|
50
|
+
it "provides a declarative to set a string pattern for the DN of the binding user" do
|
51
|
+
serviceclass = Class.new( DRbService ) do
|
52
|
+
include DRbService::LDAPAuthentication
|
53
|
+
ldap_dn TEST_DN_PATTERN
|
54
|
+
end
|
55
|
+
serviceclass.ldap_dn.should == TEST_DN_PATTERN
|
56
|
+
end
|
57
|
+
|
58
|
+
it "provides a declarative to set a search filter for finding the DN of the binding user" do
|
59
|
+
serviceclass = Class.new( DRbService ) do
|
60
|
+
include DRbService::LDAPAuthentication
|
61
|
+
ldap_dn_search TEST_FILTER_PATTERN
|
62
|
+
end
|
63
|
+
serviceclass.ldap_dn_search.should == {
|
64
|
+
:scope => :sub,
|
65
|
+
:filter => TEST_FILTER_PATTERN,
|
66
|
+
:base => nil,
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
it "accepts an optional search base dn as an argument to the search filter declarative" do
|
71
|
+
serviceclass = Class.new( DRbService ) do
|
72
|
+
include DRbService::LDAPAuthentication
|
73
|
+
ldap_dn_search TEST_FILTER_PATTERN, :base => TEST_BASE
|
74
|
+
end
|
75
|
+
serviceclass.ldap_dn_search.should == {
|
76
|
+
:filter => TEST_FILTER_PATTERN,
|
77
|
+
:base => TEST_BASE,
|
78
|
+
:scope => :sub,
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "accepts an optional scope as an argument to the search filter declarative" do
|
83
|
+
serviceclass = Class.new( DRbService ) do
|
84
|
+
include DRbService::LDAPAuthentication
|
85
|
+
ldap_dn_search TEST_FILTER_PATTERN, :scope => :one
|
86
|
+
end
|
87
|
+
serviceclass.ldap_dn_search.should == {
|
88
|
+
:filter => TEST_FILTER_PATTERN,
|
89
|
+
:scope => :one,
|
90
|
+
:base => nil,
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
it "accepts both a scope and a base as arguments to the search filter declarative" do
|
95
|
+
serviceclass = Class.new( DRbService ) do
|
96
|
+
include DRbService::LDAPAuthentication
|
97
|
+
ldap_dn_search TEST_FILTER_PATTERN, :scope => :sub, :base => 'dc=acme,dc=com'
|
98
|
+
end
|
99
|
+
serviceclass.ldap_dn_search.should == {
|
100
|
+
:filter => TEST_FILTER_PATTERN,
|
101
|
+
:scope => :sub,
|
102
|
+
:base => 'dc=acme,dc=com'
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
it "provides a declarative to set an optional authorization callback for the service" do
|
107
|
+
serviceclass = Class.new( DRbService ) do
|
108
|
+
include DRbService::LDAPAuthentication
|
109
|
+
ldap_authz_callback do |user, directory|
|
110
|
+
# noop
|
111
|
+
end
|
112
|
+
end
|
113
|
+
serviceclass.ldap_authz_callback.should be_a( Proc )
|
114
|
+
end
|
115
|
+
|
116
|
+
it "accepts the name of a method to call as the authorization callback for the service" do
|
117
|
+
serviceclass = Class.new( DRbService ) do
|
118
|
+
include DRbService::LDAPAuthentication
|
119
|
+
ldap_authz_callback :authorize_user
|
120
|
+
end
|
121
|
+
serviceclass.ldap_authz_callback.should == :authorize_user
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
describe "instances without an ldap URI set" do
|
126
|
+
|
127
|
+
before( :all ) do
|
128
|
+
@serviceclass = Class.new( DRbService ) do
|
129
|
+
def do_some_guarded_stuff; return "Ronk."; end
|
130
|
+
unguarded do
|
131
|
+
def do_some_unguarded_stuff; return "Adonk."; end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
before( :each ) do
|
137
|
+
@serviceobj = @serviceclass.new
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
it "raise an exception without calling the block on any authentication" do
|
142
|
+
block_called = false
|
143
|
+
expect {
|
144
|
+
@serviceobj.authenticate( '' ) do
|
145
|
+
block_called = true
|
146
|
+
end
|
147
|
+
}.should raise_exception( SecurityError, /authentication failure/i )
|
148
|
+
block_called.should == false
|
149
|
+
end
|
150
|
+
|
151
|
+
it "don't allow access to guarded methods" do
|
152
|
+
expect {
|
153
|
+
@serviceobj.do_some_guarded_stuff
|
154
|
+
}.to raise_exception( SecurityError, /not authenticated/i )
|
155
|
+
end
|
156
|
+
|
157
|
+
it "allow access to unguarded methods" do
|
158
|
+
@serviceobj.do_some_unguarded_stuff.should == 'Adonk.'
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "instances with an ldap_dn pattern set" do
|
164
|
+
|
165
|
+
before( :each ) do
|
166
|
+
@serviceclass = Class.new( DRbService ) do
|
167
|
+
include DRbService::LDAPAuthentication
|
168
|
+
|
169
|
+
ldap_uri TEST_URI
|
170
|
+
ldap_dn 'uid=%s,ou=people,dc=acme,dc=com'
|
171
|
+
|
172
|
+
def do_some_guarded_stuff; return "Ronk."; end
|
173
|
+
unguarded do
|
174
|
+
def do_some_unguarded_stuff; return "Adonk."; end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
before( :each ) do
|
180
|
+
@serviceobj = @serviceclass.new
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
it "uses the ldap_dn as a pattern for authentication" do
|
185
|
+
directory = mock( "directory object" )
|
186
|
+
user_branch = mock( "user branch" )
|
187
|
+
|
188
|
+
Treequel.should_receive( :directory ).with( TEST_URI ).
|
189
|
+
and_return( directory )
|
190
|
+
Treequel::Branch.should_receive( :new ).
|
191
|
+
with( directory, 'uid=user,ou=people,dc=acme,dc=com' ).
|
192
|
+
and_return( user_branch )
|
193
|
+
user_branch.should_receive( :exists? ).and_return( true )
|
194
|
+
|
195
|
+
directory.should_receive( :bind_as ).
|
196
|
+
with( user_branch, 'pass' ).
|
197
|
+
and_raise( LDAP::ResultError.new('Invalid credentials') )
|
198
|
+
|
199
|
+
block_called = false
|
200
|
+
expect {
|
201
|
+
@serviceobj.authenticate( 'user', 'pass' ) do
|
202
|
+
block_called = true
|
203
|
+
end
|
204
|
+
}.should raise_exception( SecurityError, /authentication fail/i )
|
205
|
+
block_called.should == false
|
206
|
+
end
|
207
|
+
|
208
|
+
it "fails if the DN isn't valid" do
|
209
|
+
directory = mock( "directory object" )
|
210
|
+
user_branch = mock( "user branch" )
|
211
|
+
|
212
|
+
Treequel.should_receive( :directory ).with( TEST_URI ).
|
213
|
+
and_return( directory )
|
214
|
+
Treequel::Branch.should_receive( :new ).
|
215
|
+
with( directory, 'uid=user,ou=people,dc=acme,dc=com' ).
|
216
|
+
and_return( user_branch )
|
217
|
+
user_branch.should_receive( :exists? ).and_return( false )
|
218
|
+
|
219
|
+
directory.should_not_receive( :bind_as )
|
220
|
+
|
221
|
+
block_called = false
|
222
|
+
expect {
|
223
|
+
@serviceobj.authenticate( 'user', 'pass' ) do
|
224
|
+
block_called = true
|
225
|
+
end
|
226
|
+
}.should raise_exception( SecurityError, /authentication fail/i )
|
227
|
+
block_called.should == false
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "instances with an ldap_dn_search set" do
|
233
|
+
|
234
|
+
before( :each ) do
|
235
|
+
@serviceclass = Class.new( DRbService ) do
|
236
|
+
include DRbService::LDAPAuthentication
|
237
|
+
|
238
|
+
ldap_uri TEST_URI
|
239
|
+
ldap_dn_search TEST_FILTER_PATTERN,
|
240
|
+
:base => TEST_BASE,
|
241
|
+
:scope => :one
|
242
|
+
|
243
|
+
def do_some_guarded_stuff; return "Ronk."; end
|
244
|
+
unguarded do
|
245
|
+
def do_some_unguarded_stuff; return "Adonk."; end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
before( :each ) do
|
251
|
+
@serviceobj = @serviceclass.new
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
it "use the configured search criteria to find the user to bind as" do
|
256
|
+
directory = mock( "directory object" )
|
257
|
+
base_branch = mock( "base branch" )
|
258
|
+
user_branch = mock( "user branch" )
|
259
|
+
search_branchset = mock( "search branchset" )
|
260
|
+
|
261
|
+
Treequel.should_receive( :directory ).with( TEST_URI ).
|
262
|
+
and_return( directory )
|
263
|
+
Treequel::Branch.should_receive( :new ).with( directory, TEST_BASE ).
|
264
|
+
and_return( base_branch )
|
265
|
+
base_branch.should_receive( :scope ).with( :one ).and_return( search_branchset )
|
266
|
+
|
267
|
+
expected_filter = TEST_FILTER_PATTERN % [ 'user' ]
|
268
|
+
search_branchset.should_receive( :filter ).with( expected_filter ).
|
269
|
+
and_return( search_branchset )
|
270
|
+
search_branchset.should_receive( :first ).and_return( user_branch )
|
271
|
+
|
272
|
+
directory.should_receive( :bind_as ).with( user_branch, 'pass' ).
|
273
|
+
and_raise( LDAP::ResultError.new('Invalid credentials') )
|
274
|
+
|
275
|
+
block_called = false
|
276
|
+
expect {
|
277
|
+
@serviceobj.authenticate( 'user', 'pass' ) do
|
278
|
+
block_called = true
|
279
|
+
end
|
280
|
+
}.should raise_exception( SecurityError, /authentication fail/i )
|
281
|
+
block_called.should == false
|
282
|
+
end
|
283
|
+
|
284
|
+
it "fails if the search can't find a valid user" do
|
285
|
+
directory = mock( "directory object" )
|
286
|
+
base_branch = mock( "base branch" )
|
287
|
+
search_branchset = mock( "search branchset" )
|
288
|
+
|
289
|
+
Treequel.should_receive( :directory ).with( TEST_URI ).
|
290
|
+
and_return( directory )
|
291
|
+
Treequel::Branch.should_receive( :new ).with( directory, TEST_BASE ).
|
292
|
+
and_return( base_branch )
|
293
|
+
base_branch.should_receive( :scope ).with( :one ).and_return( search_branchset )
|
294
|
+
|
295
|
+
expected_filter = TEST_FILTER_PATTERN % [ 'user' ]
|
296
|
+
search_branchset.should_receive( :filter ).with( expected_filter ).
|
297
|
+
and_return( search_branchset )
|
298
|
+
search_branchset.should_receive( :first ).and_return( nil )
|
299
|
+
|
300
|
+
directory.should_not_receive( :bound_as )
|
301
|
+
|
302
|
+
block_called = false
|
303
|
+
expect {
|
304
|
+
@serviceobj.authenticate( 'user', 'pass' ) do
|
305
|
+
block_called = true
|
306
|
+
end
|
307
|
+
}.should raise_exception( SecurityError, /authentication fail/i )
|
308
|
+
block_called.should == false
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe "instances with an ldap_authz_callback set to a Proc" do
|
313
|
+
before( :each ) do
|
314
|
+
@serviceclass = Class.new( DRbService ) do
|
315
|
+
include DRbService::LDAPAuthentication
|
316
|
+
|
317
|
+
ldap_uri TEST_URI
|
318
|
+
ldap_dn TEST_DN_PATTERN
|
319
|
+
ldap_authz_callback do |user, directory|
|
320
|
+
user.is_authorized?
|
321
|
+
end
|
322
|
+
|
323
|
+
def do_some_guarded_stuff; return "Ronk."; end
|
324
|
+
unguarded do
|
325
|
+
def do_some_unguarded_stuff; return "Adonk."; end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
before( :each ) do
|
331
|
+
@serviceobj = @serviceclass.new
|
332
|
+
end
|
333
|
+
|
334
|
+
it "raises a SecurityError if the authorization callback returns false" do
|
335
|
+
directory = mock( "directory object" )
|
336
|
+
user = mock( "user branch" )
|
337
|
+
|
338
|
+
Treequel.should_receive( :directory ).with( TEST_URI ).
|
339
|
+
and_return( directory )
|
340
|
+
expected_dn = TEST_DN_PATTERN % [ 'user' ]
|
341
|
+
Treequel::Branch.should_receive( :new ).with( directory, expected_dn ).
|
342
|
+
and_return( user )
|
343
|
+
user.should_receive( :exists? ).and_return( true )
|
344
|
+
directory.should_receive( :bind_as ).with( user, 'pass' ).
|
345
|
+
and_return( expected_dn )
|
346
|
+
user.should_receive( :is_authorized? ).and_return( false )
|
347
|
+
|
348
|
+
block_called = false
|
349
|
+
expect {
|
350
|
+
@serviceobj.authenticate( 'user', 'pass' ) do
|
351
|
+
block_called = true
|
352
|
+
end
|
353
|
+
}.should raise_exception( SecurityError, /authorization fail/i )
|
354
|
+
block_called.should == false
|
355
|
+
end
|
356
|
+
|
357
|
+
it "yields to the remote caller if the authorization callback returns true" do
|
358
|
+
directory = mock( "directory object" )
|
359
|
+
user = mock( "user branch" )
|
360
|
+
|
361
|
+
Treequel.should_receive( :directory ).with( TEST_URI ).
|
362
|
+
and_return( directory )
|
363
|
+
expected_dn = TEST_DN_PATTERN % [ 'user' ]
|
364
|
+
Treequel::Branch.should_receive( :new ).with( directory, expected_dn ).
|
365
|
+
and_return( user )
|
366
|
+
user.should_receive( :exists? ).and_return( true )
|
367
|
+
directory.should_receive( :bind_as ).with( user, 'pass' ).
|
368
|
+
and_return( expected_dn )
|
369
|
+
user.should_receive( :is_authorized? ).and_return( true )
|
370
|
+
|
371
|
+
block_called = false
|
372
|
+
@serviceobj.authenticate( 'user', 'pass' ) do
|
373
|
+
block_called = true
|
374
|
+
end
|
375
|
+
block_called.should == true
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
|