rrba 1.0.0
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/.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
|