butler 1.8.2 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/Rakefile +1 -1
  2. data/bin/botcontrol +1 -1
  3. data/data/butler/dialogs/create_config.rb +2 -2
  4. data/data/butler/dialogs/quickcreate.rb +6 -4
  5. data/data/butler/dialogs/uninstall.rb +4 -3
  6. data/data/butler/plugins/core/access.rb +10 -10
  7. data/data/butler/plugins/dev/bleakhouse.rb +19 -8
  8. data/data/butler/plugins/operator/deop.rb +10 -1
  9. data/data/butler/plugins/operator/devoice.rb +9 -0
  10. data/data/butler/plugins/operator/limit.rb +12 -0
  11. data/data/butler/plugins/operator/op.rb +9 -0
  12. data/data/butler/plugins/operator/voice.rb +10 -0
  13. data/data/butler/plugins/util/calculator.rb +11 -0
  14. data/data/butler/services/org.rubyforge.butler/calculator/1/calculator.rb +68 -0
  15. data/data/butler/services/org.rubyforge.butler/log/1/service.rb +198 -0
  16. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/acknowledge.yaml +8 -0
  17. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/gratitude.yaml +3 -0
  18. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/hello.yaml +6 -0
  19. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance.yaml +7 -0
  20. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/ignorance_about.yaml +3 -0
  21. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/insult.yaml +3 -0
  22. data/data/butler/services/org.rubyforge.butler/strings/1/data/en/rejection.yaml +12 -0
  23. data/data/butler/services/org.rubyforge.butler/strings/1/service.rb +50 -0
  24. data/lib/access.rb +6 -3
  25. data/lib/access/privilege.rb +9 -78
  26. data/lib/access/privilegelist.rb +75 -0
  27. data/lib/access/role.rb +14 -94
  28. data/lib/access/role/base.rb +40 -0
  29. data/lib/access/rolelist.rb +99 -0
  30. data/lib/access/savable.rb +6 -3
  31. data/lib/access/user.rb +21 -19
  32. data/lib/access/version.rb +17 -0
  33. data/lib/access/yamlbase.rb +64 -48
  34. data/lib/butler.rb +1 -0
  35. data/lib/butler/bot.rb +8 -2
  36. data/lib/butler/control.rb +3 -1
  37. data/lib/butler/initialvalues.rb +1 -1
  38. data/lib/butler/irc/client.rb +6 -0
  39. data/lib/butler/irc/message.rb +14 -9
  40. data/lib/butler/irc/parser.rb +8 -5
  41. data/lib/butler/irc/parser/generic.rb +33 -1
  42. data/lib/butler/irc/parser/rfc2812.rb +5 -2
  43. data/lib/butler/plugin.rb +22 -2
  44. data/lib/butler/plugins.rb +2 -7
  45. data/lib/butler/service.rb +73 -0
  46. data/lib/butler/services.rb +65 -0
  47. data/lib/butler/version.rb +1 -1
  48. data/lib/ruby/array/random.rb +17 -0
  49. data/lib/ruby/string/camelcase.rb +14 -0
  50. data/test/test_access.rb +164 -59
  51. data/test/test_access/privilege/banners.statistics.yaml +3 -0
  52. data/test/test_access/privilege/banners.yaml +3 -0
  53. data/test/test_access/privilege/news.create.yaml +3 -0
  54. data/test/test_access/privilege/news.delete.yaml +3 -0
  55. data/test/test_access/privilege/news.edit.yaml +3 -0
  56. data/test/test_access/privilege/news.read.yaml +3 -0
  57. data/test/test_access/privilege/news.yaml +3 -0
  58. data/test/test_access/privilege/paid_content.yaml +3 -0
  59. data/test/test_access/privilege/statistics.ftp.yaml +3 -0
  60. data/test/test_access/privilege/statistics.web.yaml +3 -0
  61. data/test/test_access/privilege/statistics.yaml +3 -0
  62. data/test/test_access/role/chiefeditor.yaml +7 -0
  63. data/test/test_access/role/editor.yaml +9 -0
  64. data/test/test_access/user/test.yaml +12 -0
  65. metadata +51 -5
  66. data/data/butler/plugins/core/user.rb +0 -166
  67. data/data/butler/plugins/dev/onhandlers.rb +0 -93
  68. 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
@@ -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(id())
24
+ base.save(oid())
22
25
  end
23
26
 
24
27
  # delete this record from the database
25
28
  def delete
26
- base.delete(id())
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-id if you don't want the object to be stored.
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-id #{user_id} already exists" if exists?(user_id)
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(:id, :credentials, :meta, :admin)
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 :id
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
- :id => @id,
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
- # id: a string/integer, identifying the user
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(id, credentials, meta=nil, admin=false, other={})
86
- @id = id
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 = Roles.new(self, other[:role])
92
- @privileges = Privileges.new(self, other[: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, @id) : "*"
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 && @id.eql?(other.id)
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
- @id.hash
184
+ @oid.hash
183
185
  end
184
186
 
185
187
  # :nodoc:
186
188
  def inspect
187
- "#<%s:0x%08x base: %s id: %s credentials: %s %s%s%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
- @id.inspect,
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
@@ -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
- @path = path
28
- @extender = extender
29
+ extend(extender) if extender
30
+ @path = path || default_path # default_path comes from the extender
29
31
  @data = {}
30
- extend(extender)
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 id
37
- def filename(id)
38
- "#{@path}/#{escape(id)}.yaml"
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(id)
43
- id.gsub(/[\x00-\x1f.%]/) { |m| "%%%02x"%m }.gsub("/", ".")
43
+ def escape(oid)
44
+ oid.gsub(/[\x00-\x1f.%]/) { |m| "%%%02x"%m }.gsub("/", ".")
44
45
  end
45
46
 
46
- # Extracts the record-id from a path
47
- def path2id(path)
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(id)
53
- file = filename(id)
54
- File.exist?(file) ? YAML.load_file(file) : nil
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
- Dir.glob("#{@path}/*.yaml") { |path| # /**
61
- id = path[slice]
62
- @data[id] ||= load(id)
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 id, if force_load cache will be ignored.
68
- def [](id, force_load=false)
69
- if force_load then
70
- @data[id] = load(id)
71
- else
72
- @data[id] ||= load(id)
73
- end
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
- @data[record.id] = record
79
- save(record.id)
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 delete(id)
85
- @data.delete(id)
86
- File.delete(filename(id))
93
+ def delete_oid(oid)
94
+ @lock.synchronize {
95
+ @data.delete(oid)
96
+ File.delete(filename(oid))
97
+ }
87
98
  end
88
99
 
89
- # All record id's
90
- def keys
91
- Dir.glob("#{@path}/*.yaml").map { |path|
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 id and object
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
- id = path[slice]
101
- yield(id, @data[id] || load(id))
109
+ oid = path[slice]
110
+ yield(oid, @data[oid] || load(oid))
102
111
  }
103
112
  end
104
113
 
105
- # Iterate over records, yielding id
114
+ # Iterate over records, yielding oid
106
115
  def each_key
107
116
  Dir.glob("#{@path}/*.yaml") { |path|
108
- yield(path2id(path))
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?(id)
114
- @data.has_key?(id) || File.exists?(filename(id))
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(id=nil)
120
- if @data.has_key?(id) then
121
- File.open(filename(id), 'w') { |fh| fh.write(@data[id].storable.to_yaml) }
122
- elsif id.nil? then
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 '#{id}' since it's not in @data"
141
+ raise "Could not save '#{oid}' since it's not in @data"
126
142
  end
127
143
  end
128
144
  end