rrba 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/History.txt +5 -0
- data/LICENSE +339 -0
- data/Manifest.txt +15 -0
- data/README.txt +28 -0
- data/Rakefile +37 -0
- data/install.rb +1098 -0
- data/lib/rrba.rb +3 -0
- data/lib/rrba/error.rb +6 -0
- data/lib/rrba/server.rb +59 -0
- data/lib/rrba/user.rb +38 -0
- data/test/mock.rb +149 -0
- data/test/stub/odba.rb +13 -0
- data/test/suite.rb +8 -0
- data/test/test_server.rb +178 -0
- data/test/test_user.rb +63 -0
- metadata +102 -0
data/lib/rrba.rb
ADDED
data/lib/rrba/error.rb
ADDED
data/lib/rrba/server.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Server -- rrba -- 10.11.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'rrba/user'
|
5
|
+
require 'rrba/error'
|
6
|
+
require 'openssl'
|
7
|
+
require 'digest/md5'
|
8
|
+
|
9
|
+
module RRBA
|
10
|
+
class Server
|
11
|
+
attr_writer :root, :anonymous
|
12
|
+
def initialize
|
13
|
+
@users = []
|
14
|
+
end
|
15
|
+
def add_user(user)
|
16
|
+
id = user.unique_id
|
17
|
+
if(@users.any? { |usr| usr.unique_id == id })
|
18
|
+
raise "Duplicate ID #{id}"
|
19
|
+
end
|
20
|
+
@users.push(user).last
|
21
|
+
end
|
22
|
+
def authenticate(id=nil, &block)
|
23
|
+
challenge = Digest::MD5.hexdigest(rand(2**32).to_s)[0,20]
|
24
|
+
signature = block.call(challenge)
|
25
|
+
if(@anonymous && signature == :anonymous)
|
26
|
+
return @anonymous.new_session
|
27
|
+
end
|
28
|
+
if(@root && @root.authenticate(challenge, signature))
|
29
|
+
return @root.new_session
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
if(id)
|
33
|
+
if((user = user(id)) \
|
34
|
+
&& user.authenticate(challenge, signature))
|
35
|
+
return user.new_session
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@users.each { |user|
|
39
|
+
if(user.authenticate(challenge, signature))
|
40
|
+
return user.new_session
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
rescue RuntimeError
|
45
|
+
end
|
46
|
+
raise AuthenticationError, 'Authentication failed'
|
47
|
+
end
|
48
|
+
def user(id)
|
49
|
+
@users.select { |user|
|
50
|
+
user.unique_id == id
|
51
|
+
}.first or raise "Unknown User: #{id}"
|
52
|
+
end
|
53
|
+
def unique_ids
|
54
|
+
@users.collect { |user|
|
55
|
+
user.unique_id
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/rrba/user.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# User -- rrba -- 01.11.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module RRBA
|
5
|
+
class User
|
6
|
+
attr_reader :name, :email, :short_name, :unique_id, :public_key
|
7
|
+
def initialize(unique_id)
|
8
|
+
@unique_id = unique_id or raise "Invalid ID #{unique_id}"
|
9
|
+
end
|
10
|
+
def authenticate(challenge, signature)
|
11
|
+
# enable lazy initializing
|
12
|
+
# (for subclasses that can be persisted)
|
13
|
+
public_key.sysverify(challenge, signature)
|
14
|
+
end
|
15
|
+
def email=(email)
|
16
|
+
email = email.to_s.strip
|
17
|
+
raise "Invalid Email #{email}" if(email.empty?)
|
18
|
+
@email = email
|
19
|
+
end
|
20
|
+
def name=(name)
|
21
|
+
name = name.to_s.strip
|
22
|
+
raise "Invalid Username #{name}" if(name.empty?)
|
23
|
+
@name = name
|
24
|
+
end
|
25
|
+
def new_session
|
26
|
+
raise NotImplementedError,
|
27
|
+
"no predefined behavior for RRBA::User#new_session"
|
28
|
+
end
|
29
|
+
def public_key=(key)
|
30
|
+
@public_key = key.public_key
|
31
|
+
end
|
32
|
+
def short_name=(short_name)
|
33
|
+
short_name = short_name.to_s.strip
|
34
|
+
raise "Invalid Short Name #{short_name}" if(short_name.empty?)
|
35
|
+
@short_name = short_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/test/mock.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# Ruby/Mock version 1.0
|
2
|
+
#
|
3
|
+
# A class for conveniently building mock objects in RUnit test cases.
|
4
|
+
# Copyright (c) 2001 Nat Pryce, all rights reserved
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
18
|
+
|
19
|
+
require 'runit/error'
|
20
|
+
|
21
|
+
|
22
|
+
class Mock
|
23
|
+
# Creates a new, named mock object. The name is reported in exceptions
|
24
|
+
# thrown by the mock object when method invocations are incorrect.
|
25
|
+
#
|
26
|
+
def initialize( mock_name = self.to_s )
|
27
|
+
@mock_calls = []
|
28
|
+
@next_call = 0
|
29
|
+
@name = mock_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Mock the next method call to be made to this mock object.
|
33
|
+
#
|
34
|
+
# A mock method is defined by the method name (a symbol) and a block
|
35
|
+
# that defines the arity of the method and the mocked behaviour for
|
36
|
+
# this call. The mocked behaviour should assert preconditions and
|
37
|
+
# return a value. Mocked behaviour should rarely be any more complex
|
38
|
+
# than that. If it is, that's probably an indication that the tests
|
39
|
+
# need some restructuring or that the tested code needs refactoring.
|
40
|
+
#
|
41
|
+
# If no block is given and preconditions have been defined for the named
|
42
|
+
# method, a block is created for the mocked methodthat has the same arity
|
43
|
+
# as the precondition and returns self.
|
44
|
+
#
|
45
|
+
def __next( name, &test )
|
46
|
+
if test == nil
|
47
|
+
if respond_to?( Mock.__pre(name) )
|
48
|
+
test = proc { |*args| self }
|
49
|
+
else
|
50
|
+
raise "no block given for mocked method #{name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@mock_calls.push( [name,test] )
|
54
|
+
end
|
55
|
+
|
56
|
+
# Call this at the end of a test to ensure that all scheduled calls
|
57
|
+
# have been made to the mock
|
58
|
+
#
|
59
|
+
def __verify
|
60
|
+
if @next_call != @mock_calls.length
|
61
|
+
raise RUNIT::AssertionFailedError,
|
62
|
+
"not all expected method calls were made to #{@name}",
|
63
|
+
caller
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
private
|
69
|
+
# Dispatches aribtrary method calls to the next mocked behaviour
|
70
|
+
#
|
71
|
+
def method_missing( name, *args )
|
72
|
+
__mock_call( name, args, (block_given? ? proc : nil) )
|
73
|
+
end
|
74
|
+
|
75
|
+
# Implements a method call using the next mocked behaviour and asserts
|
76
|
+
# that the expected method is called with the expected number of
|
77
|
+
# arguments.
|
78
|
+
#
|
79
|
+
def __mock_call( name, args, block )
|
80
|
+
if @next_call >= @mock_calls.length
|
81
|
+
raise RUNIT::AssertionFailedError,
|
82
|
+
"unexpected call to #{name} method of #{@name}",
|
83
|
+
caller(2)
|
84
|
+
end
|
85
|
+
|
86
|
+
expected_name,body = @mock_calls[@next_call]
|
87
|
+
@next_call += 1
|
88
|
+
|
89
|
+
if name != expected_name
|
90
|
+
raise RUNIT::AssertionFailedError,
|
91
|
+
"wrong method called on #{@name}; " +
|
92
|
+
"expected #{expected_name}, was #{name}",
|
93
|
+
caller(2)
|
94
|
+
end
|
95
|
+
|
96
|
+
args_length = args.length + (block ? 1 : 0)
|
97
|
+
|
98
|
+
if body.arity < 0
|
99
|
+
if (body.arity+1).abs > args_length
|
100
|
+
raise RUNIT::AssertionFailedError,
|
101
|
+
"too few arguments to #{name} method of #{@name}; " +
|
102
|
+
"require #{(body.arity+1).abs}, got #{args.length}",
|
103
|
+
caller(2)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
if body.arity != args_length
|
107
|
+
raise RUNIT::AssertionFailedError,
|
108
|
+
"wrong number of arguments to " +
|
109
|
+
"#{name} method of #{@name}; " +
|
110
|
+
"require #{body.arity}, got #{args.length}",
|
111
|
+
caller(2)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if respond_to? Mock.__pre(name)
|
116
|
+
if block
|
117
|
+
precondition_ok = __send__( Mock.__pre(name), *args, &block )
|
118
|
+
else
|
119
|
+
precondition_ok = __send__( Mock.__pre(name), *args )
|
120
|
+
end
|
121
|
+
|
122
|
+
if not precondition_ok
|
123
|
+
raise RUNIT::AssertionFailedError,
|
124
|
+
"precondition of #{name} method violated",
|
125
|
+
caller(2)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if block
|
130
|
+
instance_eval { body.call( block, *args ) }
|
131
|
+
else
|
132
|
+
instance_eval { body.call( *args ) }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# The name of a precondition for a method
|
137
|
+
def Mock.__pre( method )
|
138
|
+
"__pre_#{method.to_i}".intern
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def Mock.method_added( name )
|
143
|
+
unless(/^__pre_/.match(name.to_s))
|
144
|
+
pre = self.__pre(name)
|
145
|
+
alias_method( pre, name )
|
146
|
+
undef_method(name)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/test/stub/odba.rb
ADDED
data/test/suite.rb
ADDED
data/test/test_server.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# TestServer -- rdpm -- 10.11.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
5
|
+
$: << File.dirname(__FILE__)
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
require 'mock'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'rrba/server'
|
11
|
+
|
12
|
+
module RRBA
|
13
|
+
class TestServer < Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
@server = Server.new
|
16
|
+
@datadir = File.expand_path('data', File.dirname(__FILE__))
|
17
|
+
end
|
18
|
+
def test_add_user
|
19
|
+
user = User.new(345)
|
20
|
+
users = @server.instance_variable_get('@users')
|
21
|
+
@server.add_user(user)
|
22
|
+
assert_equal(user, @server.user(345))
|
23
|
+
end
|
24
|
+
def test_add_user__duplicate_id
|
25
|
+
user = User.new(345)
|
26
|
+
users = @server.instance_variable_get('@users')
|
27
|
+
@server.add_user(user)
|
28
|
+
assert_equal(user, @server.user(345))
|
29
|
+
assert_equal(345, user.unique_id)
|
30
|
+
user = User.new(345)
|
31
|
+
assert_raises(RuntimeError) {
|
32
|
+
@server.add_user(user)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
def test_authenticate__failure
|
36
|
+
user1 = Mock.new('User1')
|
37
|
+
user2 = Mock.new('User2')
|
38
|
+
user3 = Mock.new('User3')
|
39
|
+
user1.__next(:authenticate) { |chal, sig|
|
40
|
+
assert_equal(20, chal.size)
|
41
|
+
assert_equal('client-signature', sig)
|
42
|
+
false
|
43
|
+
}
|
44
|
+
user2.__next(:authenticate) { |chal, sig|
|
45
|
+
assert_equal(20, chal.size)
|
46
|
+
assert_equal('client-signature', sig)
|
47
|
+
false
|
48
|
+
}
|
49
|
+
user3.__next(:authenticate) { |chal, sig|
|
50
|
+
assert_equal(20, chal.size)
|
51
|
+
assert_equal('client-signature', sig)
|
52
|
+
false
|
53
|
+
}
|
54
|
+
users = [
|
55
|
+
user1,
|
56
|
+
user2,
|
57
|
+
user3,
|
58
|
+
]
|
59
|
+
@server.instance_variable_set('@users', users)
|
60
|
+
user = nil
|
61
|
+
assert_raises(AuthenticationError) {
|
62
|
+
user = @server.authenticate { |challenge|
|
63
|
+
assert_equal(20, challenge.size)
|
64
|
+
'client-signature'
|
65
|
+
}
|
66
|
+
}
|
67
|
+
assert_nil(user)
|
68
|
+
end
|
69
|
+
def test_authenticate__success
|
70
|
+
user1 = Mock.new('User1')
|
71
|
+
user2 = Mock.new('User2')
|
72
|
+
user3 = Mock.new('User3')
|
73
|
+
user1.__next(:authenticate) { |challenge, sig|
|
74
|
+
assert_equal('client-signature', sig)
|
75
|
+
false
|
76
|
+
}
|
77
|
+
user2.__next(:authenticate) { |challenge, sig|
|
78
|
+
assert_equal('client-signature', sig)
|
79
|
+
true
|
80
|
+
}
|
81
|
+
user3.__next(:authenticate) { |challenge, sig|
|
82
|
+
assert_equal('client-signature', sig)
|
83
|
+
false
|
84
|
+
}
|
85
|
+
users = [
|
86
|
+
user1,
|
87
|
+
user2,
|
88
|
+
user3,
|
89
|
+
]
|
90
|
+
session = Mock.new('Session')
|
91
|
+
user2.__next(:new_session) { session }
|
92
|
+
@server.instance_variable_set('@users', users)
|
93
|
+
sess = @server.authenticate { |challenge, sig|
|
94
|
+
assert_equal(20, challenge.size)
|
95
|
+
'client-signature'
|
96
|
+
}
|
97
|
+
assert_equal(session, sess)
|
98
|
+
user2.__verify
|
99
|
+
end
|
100
|
+
def test_authenticate__root
|
101
|
+
root = Mock.new('Root')
|
102
|
+
user2 = Mock.new('User2')
|
103
|
+
user3 = Mock.new('User3')
|
104
|
+
root.__next(:authenticate) { |challenge, sig|
|
105
|
+
assert_equal('client-signature', sig)
|
106
|
+
true
|
107
|
+
}
|
108
|
+
users = [
|
109
|
+
user2,
|
110
|
+
user3,
|
111
|
+
]
|
112
|
+
session = Mock.new('Session')
|
113
|
+
root.__next(:new_session) { session }
|
114
|
+
@server.instance_variable_set('@root', root)
|
115
|
+
@server.instance_variable_set('@users', users)
|
116
|
+
sess = @server.authenticate { |challenge, sig|
|
117
|
+
'client-signature'
|
118
|
+
}
|
119
|
+
assert_equal(session, sess)
|
120
|
+
root.__verify
|
121
|
+
end
|
122
|
+
def test_authenticate__anonymous
|
123
|
+
root = Mock.new('Root')
|
124
|
+
user2 = Mock.new('User2')
|
125
|
+
user3 = Mock.new('User3')
|
126
|
+
anonymous = Mock.new('Anonymous')
|
127
|
+
users = [
|
128
|
+
user2,
|
129
|
+
user3,
|
130
|
+
]
|
131
|
+
session = Mock.new('Session')
|
132
|
+
anonymous.__next(:new_session) { session }
|
133
|
+
@server.root = root
|
134
|
+
@server.anonymous = anonymous
|
135
|
+
@server.instance_variable_set('@users', users)
|
136
|
+
sess = @server.authenticate { |challenge, sig|
|
137
|
+
:anonymous
|
138
|
+
}
|
139
|
+
assert_equal(session, sess)
|
140
|
+
root.__verify
|
141
|
+
end
|
142
|
+
def test_authenticate__by_id
|
143
|
+
user1 = Mock.new('User1')
|
144
|
+
user2 = Mock.new('User2')
|
145
|
+
user3 = Mock.new('User3')
|
146
|
+
user1.__next(:unique_id) { 'user1' }
|
147
|
+
user2.__next(:unique_id) { 'user2' }
|
148
|
+
user3.__next(:unique_id) { 'user3' }
|
149
|
+
user2.__next(:authenticate) { |challenge, sig|
|
150
|
+
assert_equal('client-signature', sig)
|
151
|
+
true
|
152
|
+
}
|
153
|
+
users = [
|
154
|
+
user1,
|
155
|
+
user2,
|
156
|
+
user3,
|
157
|
+
]
|
158
|
+
session = Mock.new('Session')
|
159
|
+
user2.__next(:new_session) { session }
|
160
|
+
@server.instance_variable_set('@users', users)
|
161
|
+
sess = @server.authenticate('user2') { |challenge, sig|
|
162
|
+
assert_equal(20, challenge.size)
|
163
|
+
'client-signature'
|
164
|
+
}
|
165
|
+
assert_equal(session, sess)
|
166
|
+
user2.__verify
|
167
|
+
end
|
168
|
+
def test_unique_ids
|
169
|
+
user = Mock.new('user')
|
170
|
+
user2 = Mock.new('user2')
|
171
|
+
user.__next(:unique_id) { 'rwaltert' }
|
172
|
+
user2.__next(:unique_id) { 'hwyss' }
|
173
|
+
users = [user, user2]
|
174
|
+
@server.instance_variable_set('@users', users)
|
175
|
+
assert_equal(["rwaltert", 'hwyss'], @server.unique_ids)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|