butler 1.8.2 → 1.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/bin/botcontrol +1 -1
- data/data/butler/dialogs/create_config.rb +2 -2
- data/data/butler/dialogs/quickcreate.rb +6 -4
- data/data/butler/dialogs/uninstall.rb +4 -3
- data/data/butler/plugins/core/access.rb +10 -10
- data/data/butler/plugins/dev/bleakhouse.rb +19 -8
- data/data/butler/plugins/operator/deop.rb +10 -1
- data/data/butler/plugins/operator/devoice.rb +9 -0
- data/data/butler/plugins/operator/limit.rb +12 -0
- data/data/butler/plugins/operator/op.rb +9 -0
- data/data/butler/plugins/operator/voice.rb +10 -0
- data/data/butler/plugins/util/calculator.rb +11 -0
- data/data/butler/services/org.rubyforge.butler/calculator/1/calculator.rb +68 -0
- data/data/butler/services/org.rubyforge.butler/log/1/service.rb +198 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/acknowledge.yaml +8 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/gratitude.yaml +3 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/hello.yaml +6 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance.yaml +7 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance_about.yaml +3 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/insult.yaml +3 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/data/en/rejection.yaml +12 -0
- data/data/butler/services/org.rubyforge.butler/strings/1/service.rb +50 -0
- data/lib/access.rb +6 -3
- data/lib/access/privilege.rb +9 -78
- data/lib/access/privilegelist.rb +75 -0
- data/lib/access/role.rb +14 -94
- data/lib/access/role/base.rb +40 -0
- data/lib/access/rolelist.rb +99 -0
- data/lib/access/savable.rb +6 -3
- data/lib/access/user.rb +21 -19
- data/lib/access/version.rb +17 -0
- data/lib/access/yamlbase.rb +64 -48
- data/lib/butler.rb +1 -0
- data/lib/butler/bot.rb +8 -2
- data/lib/butler/control.rb +3 -1
- data/lib/butler/initialvalues.rb +1 -1
- data/lib/butler/irc/client.rb +6 -0
- data/lib/butler/irc/message.rb +14 -9
- data/lib/butler/irc/parser.rb +8 -5
- data/lib/butler/irc/parser/generic.rb +33 -1
- data/lib/butler/irc/parser/rfc2812.rb +5 -2
- data/lib/butler/plugin.rb +22 -2
- data/lib/butler/plugins.rb +2 -7
- data/lib/butler/service.rb +73 -0
- data/lib/butler/services.rb +65 -0
- data/lib/butler/version.rb +1 -1
- data/lib/ruby/array/random.rb +17 -0
- data/lib/ruby/string/camelcase.rb +14 -0
- data/test/test_access.rb +164 -59
- data/test/test_access/privilege/banners.statistics.yaml +3 -0
- data/test/test_access/privilege/banners.yaml +3 -0
- data/test/test_access/privilege/news.create.yaml +3 -0
- data/test/test_access/privilege/news.delete.yaml +3 -0
- data/test/test_access/privilege/news.edit.yaml +3 -0
- data/test/test_access/privilege/news.read.yaml +3 -0
- data/test/test_access/privilege/news.yaml +3 -0
- data/test/test_access/privilege/paid_content.yaml +3 -0
- data/test/test_access/privilege/statistics.ftp.yaml +3 -0
- data/test/test_access/privilege/statistics.web.yaml +3 -0
- data/test/test_access/privilege/statistics.yaml +3 -0
- data/test/test_access/role/chiefeditor.yaml +7 -0
- data/test/test_access/role/editor.yaml +9 -0
- data/test/test_access/user/test.yaml +12 -0
- metadata +51 -5
- data/data/butler/plugins/core/user.rb +0 -166
- data/data/butler/plugins/dev/onhandlers.rb +0 -93
- data/data/butler/plugins/service/log.rb +0 -183
@@ -0,0 +1,40 @@
|
|
1
|
+
class Access
|
2
|
+
class Role
|
3
|
+
|
4
|
+
# The module to extend the Database manager
|
5
|
+
module Base
|
6
|
+
|
7
|
+
# Create a new Role
|
8
|
+
# role is a role-oid, should be \w+
|
9
|
+
# description is a piece of text describing the role
|
10
|
+
# privileges are the privileges the role provides
|
11
|
+
def create(role, description=nil, privilege_oids=[], role_oids=[])
|
12
|
+
raise "Role #{role} already exists" if exists?(role)
|
13
|
+
role = Role.new(role, description)
|
14
|
+
role.access = access
|
15
|
+
role.base = self
|
16
|
+
add(role)
|
17
|
+
role_oids.each { |role_oid|
|
18
|
+
role.roles.add_oid(role_oid)
|
19
|
+
}
|
20
|
+
privilege_oids.each { |priv|
|
21
|
+
role.privileges.add_oid(priv)
|
22
|
+
}
|
23
|
+
role
|
24
|
+
end
|
25
|
+
|
26
|
+
# Restore an Access::Privilege from it's storable data
|
27
|
+
def load(*args) # :nodoc:
|
28
|
+
return nil unless data = super
|
29
|
+
roles = access.role
|
30
|
+
data[:roles] = data[:roles].map { |role| roles[role] }
|
31
|
+
array = data.values_at(:oid, :description)
|
32
|
+
array << data
|
33
|
+
role = Role.new(*array)
|
34
|
+
role.access = access
|
35
|
+
role.base = self
|
36
|
+
role
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
require 'access'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
class Access
|
14
|
+
# A list of roles
|
15
|
+
class RoleList
|
16
|
+
include Enumerable
|
17
|
+
|
18
|
+
# owner must be capable of #save and #
|
19
|
+
# roles is the list of Role instances (array)
|
20
|
+
def initialize(owner, roles=nil)
|
21
|
+
@owner = owner
|
22
|
+
@roles = {}
|
23
|
+
roles.each { |role| @roles[role] = @owner.access.role[role] } if roles
|
24
|
+
end
|
25
|
+
|
26
|
+
# Tests if any of the roles in self allows a privilege under
|
27
|
+
# given conditions (may be nil to indicate no condition)
|
28
|
+
def allow?(privilege, condition=nil)
|
29
|
+
@roles.any? { |oid, role| role.allows?(privilege, condition) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# add a role (Role instance)
|
33
|
+
def add(role)
|
34
|
+
@roles[role.oid] = role
|
35
|
+
@owner.save
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_oid(role_oid)
|
39
|
+
role = @owner.access.role[role_oid]
|
40
|
+
raise "Role #{role_oid} does not exist" unless role
|
41
|
+
add(role)
|
42
|
+
end
|
43
|
+
|
44
|
+
# all roles
|
45
|
+
def list
|
46
|
+
@roles.dup
|
47
|
+
end
|
48
|
+
|
49
|
+
# Iterate over the roles (oid & role)
|
50
|
+
# :yield: role_oid, role
|
51
|
+
def each(&block)
|
52
|
+
@roles.each(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Iterate over the role oids
|
56
|
+
def each_oid(&block)
|
57
|
+
@roles.each_key(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Iterate over the roles
|
61
|
+
def each_role(&block)
|
62
|
+
@roles.each_value(&block)
|
63
|
+
end
|
64
|
+
|
65
|
+
# :nodoc:
|
66
|
+
# prepare for storage
|
67
|
+
def storable
|
68
|
+
@roles.map { |oid, role| oid }
|
69
|
+
end
|
70
|
+
|
71
|
+
# :nodoc:
|
72
|
+
def access
|
73
|
+
@owner.access
|
74
|
+
end
|
75
|
+
|
76
|
+
# delete a role (Role instance)
|
77
|
+
def remove(role)
|
78
|
+
remove_oid(role.oid)
|
79
|
+
end
|
80
|
+
|
81
|
+
# delete a role by its oid
|
82
|
+
def remove_oid(role_oid)
|
83
|
+
@roles.delete(role_oid)
|
84
|
+
@owner.save
|
85
|
+
end
|
86
|
+
|
87
|
+
def save
|
88
|
+
@owner.save
|
89
|
+
end
|
90
|
+
|
91
|
+
def inspect # :nodoc:
|
92
|
+
"#<%s:0x%08x roles=%s>" % [
|
93
|
+
self.class,
|
94
|
+
object_id << 1,
|
95
|
+
@roles.map { |oid, role| oid }.join(', '),
|
96
|
+
]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/access/savable.rb
CHANGED
@@ -7,7 +7,10 @@
|
|
7
7
|
|
8
8
|
|
9
9
|
class Access
|
10
|
+
|
10
11
|
# Mixin for classes representing records
|
12
|
+
# Requires a method base (Savable creates an accessor to @base) and
|
13
|
+
# an #oid method.
|
11
14
|
module Savable
|
12
15
|
|
13
16
|
# The Access instance that manages this records base
|
@@ -15,15 +18,15 @@ class Access
|
|
15
18
|
|
16
19
|
# The "Base" instance that manages this record
|
17
20
|
attr_accessor :base
|
18
|
-
|
21
|
+
|
19
22
|
# save changes to this record
|
20
23
|
def save
|
21
|
-
base.save(
|
24
|
+
base.save(oid())
|
22
25
|
end
|
23
26
|
|
24
27
|
# delete this record from the database
|
25
28
|
def delete
|
26
|
-
base.delete(
|
29
|
+
base.delete(oid())
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|
data/lib/access/user.rb
CHANGED
@@ -14,24 +14,24 @@ require 'access/admin'
|
|
14
14
|
|
15
15
|
class Access
|
16
16
|
# Access::User
|
17
|
-
# Use nil-
|
17
|
+
# Use nil-oid if you don't want the object to be stored.
|
18
18
|
class User
|
19
19
|
include Savable
|
20
20
|
|
21
21
|
module Base
|
22
22
|
# Create a new - inactive(!) - user
|
23
23
|
def create(user_id, credentials, meta=nil, admin=false, opt={})
|
24
|
-
raise "User-
|
24
|
+
raise "User-oid #{user_id} already exists" if exists?(user_id)
|
25
25
|
credentials = credentials ? access.hash_credentials(credentials, user_id) : "*"
|
26
26
|
user = User.new(
|
27
|
+
access,
|
28
|
+
self,
|
27
29
|
user_id,
|
28
30
|
credentials,
|
29
31
|
meta,
|
30
32
|
admin,
|
31
|
-
{:active => !!opt.delete(:active)}.merge(opt)
|
33
|
+
{ :active => !!opt.delete(:active) }.merge(opt)
|
32
34
|
)
|
33
|
-
user.access = access
|
34
|
-
user.base = self
|
35
35
|
add(user)
|
36
36
|
user
|
37
37
|
end
|
@@ -39,9 +39,9 @@ class Access
|
|
39
39
|
# Restore an Access::User from it's storable data
|
40
40
|
def load(*args) # :nodoc:
|
41
41
|
return nil unless data = super
|
42
|
-
array = data.values_at(:
|
42
|
+
array = data.values_at(:oid, :credentials, :meta, :admin)
|
43
43
|
array << data
|
44
|
-
user = User.new(*array)
|
44
|
+
user = User.new(access, self, *array)
|
45
45
|
user.access = access
|
46
46
|
user.base = self
|
47
47
|
user
|
@@ -49,7 +49,7 @@ class Access
|
|
49
49
|
end
|
50
50
|
|
51
51
|
# The record-id
|
52
|
-
attr_reader :
|
52
|
+
attr_reader :oid
|
53
53
|
|
54
54
|
# The credentials of the user (password)
|
55
55
|
attr_reader :credentials
|
@@ -67,7 +67,7 @@ class Access
|
|
67
67
|
# Simplified to hashes and scalar values.
|
68
68
|
def storable
|
69
69
|
{
|
70
|
-
:
|
70
|
+
:oid => @oid,
|
71
71
|
:credentials => @credentials,
|
72
72
|
:meta => @meta,
|
73
73
|
:admin => @admin,
|
@@ -78,18 +78,20 @@ class Access
|
|
78
78
|
end
|
79
79
|
|
80
80
|
# access: the access-instance the user is tied to (necessary for storing)
|
81
|
-
#
|
81
|
+
# oid: a string/integer, identifying the user
|
82
82
|
# credentials: (if not subclassed) a string authenticating the user, will be hashed before storing.
|
83
83
|
# meta: meta data about the user
|
84
84
|
# admin: admin's have all privileges granted
|
85
|
-
def initialize(
|
86
|
-
@
|
85
|
+
def initialize(access, base, oid, credentials, meta=nil, admin=false, other={})
|
86
|
+
@access = access
|
87
|
+
@base = base
|
88
|
+
@oid = oid
|
87
89
|
@credentials = credentials
|
88
90
|
@admin = admin
|
89
91
|
@active = other.has_key?(:active) ? other[:active] : false
|
90
92
|
@meta = meta
|
91
|
-
@roles =
|
92
|
-
@privileges =
|
93
|
+
@roles = RoleList.new(self, other[:roles])
|
94
|
+
@privileges = PrivilegeList.new(self, other[:privileges])
|
93
95
|
@logged = false
|
94
96
|
|
95
97
|
extend(Admin) if @admin
|
@@ -100,7 +102,7 @@ class Access
|
|
100
102
|
# accepted as correct) or a plaintext password, the method uses
|
101
103
|
# Access#hash_credentials to hash the passed in value.
|
102
104
|
def credentials=(value)
|
103
|
-
@credentials = value ? access.hash_credentials(value, @
|
105
|
+
@credentials = value ? access.hash_credentials(value, @oid) : "*"
|
104
106
|
save
|
105
107
|
end
|
106
108
|
|
@@ -173,22 +175,22 @@ class Access
|
|
173
175
|
|
174
176
|
# :nodoc:
|
175
177
|
def eql?(other)
|
176
|
-
self.class == other.class && @
|
178
|
+
self.class == other.class && @oid.eql?(other.oid)
|
177
179
|
end
|
178
180
|
alias == eql? # in ruby1.8 that's unecessary as == uses eql? per default
|
179
181
|
|
180
182
|
# :nodoc:
|
181
183
|
def hash
|
182
|
-
@
|
184
|
+
@oid.hash
|
183
185
|
end
|
184
186
|
|
185
187
|
# :nodoc:
|
186
188
|
def inspect
|
187
|
-
"#<%s:0x%08x base: %s
|
189
|
+
"#<%s:0x%08x base: %s oid: %s credentials: %s %s%s%s>" % [
|
188
190
|
self.class,
|
189
191
|
object_id << 1,
|
190
192
|
"#{@base.class}(#{(class <<@base; self; end).ancestors.first})",
|
191
|
-
@
|
193
|
+
@oid.inspect,
|
192
194
|
@credentials,
|
193
195
|
@active ? 'active' : 'inactive',
|
194
196
|
admin? ? ' admin' : '',
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2007 by Stefan Rusterholz.
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE.txt for permissions.
|
5
|
+
#++
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
class Access
|
10
|
+
module VERSION # :nodoc:
|
11
|
+
MAJOR = 1
|
12
|
+
MINOR = 0
|
13
|
+
TINY = 0
|
14
|
+
STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
|
15
|
+
def self.to_s; STRING; end
|
16
|
+
end
|
17
|
+
end
|
data/lib/access/yamlbase.rb
CHANGED
@@ -7,16 +7,18 @@
|
|
7
7
|
|
8
8
|
|
9
9
|
require 'access'
|
10
|
+
require 'thread'
|
10
11
|
require 'yaml'
|
11
12
|
|
12
13
|
|
13
14
|
|
14
15
|
class Access
|
15
16
|
|
16
|
-
# An Access compatible storage backend
|
17
|
+
# An Access compatible storage backend, based on YAML
|
17
18
|
class YAMLBase
|
18
19
|
include Enumerable
|
19
20
|
|
21
|
+
# The access instance this base is tied to
|
20
22
|
attr_accessor :access
|
21
23
|
|
22
24
|
# extender: a module that will extend this instance and provide additional
|
@@ -24,105 +26,119 @@ class Access
|
|
24
26
|
# framework: the Access instance this data-storage is tied to
|
25
27
|
# path: the path to the data
|
26
28
|
def initialize(extender, path=nil)
|
27
|
-
|
28
|
-
@
|
29
|
+
extend(extender) if extender
|
30
|
+
@path = path || default_path # default_path comes from the extender
|
29
31
|
@data = {}
|
30
|
-
|
32
|
+
@lock = Mutex.new
|
31
33
|
|
32
|
-
@path ||= default_path
|
33
34
|
raise "Path must be a directory (#@path)" unless File.directory?(@path)
|
34
35
|
end
|
35
36
|
|
36
|
-
# The full path and filename to the data object belonging to
|
37
|
-
def filename(
|
38
|
-
"#{@path}/#{escape(
|
37
|
+
# The full path and filename to the data object belonging to oid
|
38
|
+
def filename(oid)
|
39
|
+
"#{@path}/#{escape(oid)}.yaml"
|
39
40
|
end
|
40
41
|
|
41
42
|
# Escapes path names
|
42
|
-
def escape(
|
43
|
-
|
43
|
+
def escape(oid)
|
44
|
+
oid.gsub(/[\x00-\x1f.%]/) { |m| "%%%02x"%m }.gsub("/", ".")
|
44
45
|
end
|
45
46
|
|
46
|
-
# Extracts the record-
|
47
|
-
def
|
47
|
+
# Extracts the record-oid from a path
|
48
|
+
def path2oid(path)
|
48
49
|
path[(@path.length+1)..-6].gsub(".", "/").gsub(/%([\dA-Fa-f]{2})/) { $1.to_i(16).chr }
|
49
50
|
end
|
50
51
|
|
51
52
|
# Load an entry, will not write to cache, returns nil if entry doesn't exist
|
52
|
-
def load(
|
53
|
-
file = filename(
|
54
|
-
|
53
|
+
def load(oid)
|
54
|
+
file = filename(oid)
|
55
|
+
YAML.load_file(file)
|
56
|
+
rescue Errno::ENOENT
|
57
|
+
nil
|
55
58
|
end
|
56
59
|
|
57
60
|
# Loads all entries into cache
|
58
61
|
def cache_all
|
59
62
|
slice = (@path.length+1)..-6
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
@lock.synchronize {
|
64
|
+
Dir.glob("#{@path}/*.yaml") { |path| # /**
|
65
|
+
oid = path[slice]
|
66
|
+
@data[oid] ||= load(oid)
|
67
|
+
}
|
63
68
|
}
|
64
69
|
end
|
65
70
|
|
66
71
|
|
67
|
-
# Retrieve the object with
|
68
|
-
def [](
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
# Retrieve the object with oid, if force_load cache will be ignored.
|
73
|
+
def [](oid, force_load=false)
|
74
|
+
@lock.synchronize {
|
75
|
+
if force_load then
|
76
|
+
@data[oid] = load(oid)
|
77
|
+
else
|
78
|
+
@data[oid] ||= load(oid)
|
79
|
+
end
|
80
|
+
}
|
74
81
|
end
|
75
82
|
|
76
83
|
# Add a record to the storage
|
77
84
|
def <<(record)
|
78
|
-
@
|
79
|
-
|
85
|
+
@lock.synchronize {
|
86
|
+
@data[record.oid] = record
|
87
|
+
save(record.oid)
|
88
|
+
}
|
80
89
|
end
|
81
90
|
alias add <<
|
82
91
|
|
83
92
|
# Delete a record from the storage
|
84
|
-
def
|
85
|
-
@
|
86
|
-
|
93
|
+
def delete_oid(oid)
|
94
|
+
@lock.synchronize {
|
95
|
+
@data.delete(oid)
|
96
|
+
File.delete(filename(oid))
|
97
|
+
}
|
87
98
|
end
|
88
99
|
|
89
|
-
#
|
90
|
-
def
|
91
|
-
|
92
|
-
path2id(path)
|
93
|
-
}
|
100
|
+
# Delete a record from the storage
|
101
|
+
def delete(record)
|
102
|
+
delete_oid(record.oid)
|
94
103
|
end
|
95
|
-
|
96
|
-
# Iterate over records, yielding
|
104
|
+
|
105
|
+
# Iterate over records, yielding oid and object
|
97
106
|
def each
|
98
107
|
slice = (@path.length+1)..-6
|
99
108
|
Dir.glob("#{@path}/*.yaml") { |path|
|
100
|
-
|
101
|
-
yield(
|
109
|
+
oid = path[slice]
|
110
|
+
yield(oid, @data[oid] || load(oid))
|
102
111
|
}
|
103
112
|
end
|
104
113
|
|
105
|
-
# Iterate over records, yielding
|
114
|
+
# Iterate over records, yielding oid
|
106
115
|
def each_key
|
107
116
|
Dir.glob("#{@path}/*.yaml") { |path|
|
108
|
-
yield(
|
117
|
+
yield(path2oid(path))
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
# All record oid's
|
122
|
+
def keys
|
123
|
+
Dir.glob("#{@path}/*.yaml").map { |path|
|
124
|
+
path2oid(path)
|
109
125
|
}
|
110
126
|
end
|
111
127
|
|
112
128
|
# Check existency of an object.
|
113
|
-
def exists?(
|
114
|
-
@data.has_key?(
|
129
|
+
def exists?(oid)
|
130
|
+
@data.has_key?(oid) || File.exists?(filename(oid))
|
115
131
|
end
|
116
132
|
alias exist? exists?
|
117
133
|
|
118
134
|
# Synchronize data-cache with filesystem.
|
119
|
-
def save(
|
120
|
-
if @data.has_key?(
|
121
|
-
File.open(filename(
|
122
|
-
elsif
|
135
|
+
def save(oid=nil)
|
136
|
+
if @data.has_key?(oid) then
|
137
|
+
File.open(filename(oid), 'w') { |fh| fh.write(@data[oid].storable.to_yaml) }
|
138
|
+
elsif oid.nil? then
|
123
139
|
@data.each_key { |key| save(key) }
|
124
140
|
else
|
125
|
-
raise "Could not save '#{
|
141
|
+
raise "Could not save '#{oid}' since it's not in @data"
|
126
142
|
end
|
127
143
|
end
|
128
144
|
end
|