butler 1.8.2 → 1.8.3
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/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
|