butler 1.8.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.
Files changed (164) hide show
  1. data/CHANGELOG +4 -0
  2. data/GPL.txt +340 -0
  3. data/LICENSE.txt +52 -0
  4. data/README +37 -0
  5. data/Rakefile +334 -0
  6. data/bin/botcontrol +230 -0
  7. data/data/butler/config_template.yaml +4 -0
  8. data/data/butler/dialogs/backup.rb +19 -0
  9. data/data/butler/dialogs/botcontrol.rb +4 -0
  10. data/data/butler/dialogs/config.rb +1 -0
  11. data/data/butler/dialogs/create.rb +53 -0
  12. data/data/butler/dialogs/delete.rb +3 -0
  13. data/data/butler/dialogs/en/backup.yaml +6 -0
  14. data/data/butler/dialogs/en/botcontrol.yaml +5 -0
  15. data/data/butler/dialogs/en/create.yaml +11 -0
  16. data/data/butler/dialogs/en/delete.yaml +2 -0
  17. data/data/butler/dialogs/en/help.yaml +17 -0
  18. data/data/butler/dialogs/en/info.yaml +13 -0
  19. data/data/butler/dialogs/en/list.yaml +4 -0
  20. data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
  21. data/data/butler/dialogs/en/rename.yaml +3 -0
  22. data/data/butler/dialogs/en/start.yaml +3 -0
  23. data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
  24. data/data/butler/dialogs/en/uninstall.yaml +5 -0
  25. data/data/butler/dialogs/en/unknown_command.yaml +2 -0
  26. data/data/butler/dialogs/help.rb +11 -0
  27. data/data/butler/dialogs/info.rb +27 -0
  28. data/data/butler/dialogs/interactive.rb +1 -0
  29. data/data/butler/dialogs/list.rb +10 -0
  30. data/data/butler/dialogs/notyetimplemented.rb +1 -0
  31. data/data/butler/dialogs/rename.rb +4 -0
  32. data/data/butler/dialogs/selectbot.rb +2 -0
  33. data/data/butler/dialogs/start.rb +5 -0
  34. data/data/butler/dialogs/sync_plugins.rb +30 -0
  35. data/data/butler/dialogs/uninstall.rb +17 -0
  36. data/data/butler/dialogs/unknown_command.rb +1 -0
  37. data/data/butler/plugins/core/logout.rb +41 -0
  38. data/data/butler/plugins/core/plugins.rb +134 -0
  39. data/data/butler/plugins/core/privilege.rb +103 -0
  40. data/data/butler/plugins/core/user.rb +166 -0
  41. data/data/butler/plugins/dev/eval.rb +64 -0
  42. data/data/butler/plugins/dev/nometa.rb +14 -0
  43. data/data/butler/plugins/dev/onhandlers.rb +93 -0
  44. data/data/butler/plugins/dev/raw.rb +36 -0
  45. data/data/butler/plugins/dev/rawlog.rb +77 -0
  46. data/data/butler/plugins/games/eightball.rb +54 -0
  47. data/data/butler/plugins/games/mastermind.rb +174 -0
  48. data/data/butler/plugins/irc/action.rb +36 -0
  49. data/data/butler/plugins/irc/join.rb +38 -0
  50. data/data/butler/plugins/irc/notice.rb +36 -0
  51. data/data/butler/plugins/irc/part.rb +38 -0
  52. data/data/butler/plugins/irc/privmsg.rb +36 -0
  53. data/data/butler/plugins/irc/quit.rb +36 -0
  54. data/data/butler/plugins/operator/deop.rb +41 -0
  55. data/data/butler/plugins/operator/devoice.rb +41 -0
  56. data/data/butler/plugins/operator/limit.rb +47 -0
  57. data/data/butler/plugins/operator/op.rb +41 -0
  58. data/data/butler/plugins/operator/voice.rb +41 -0
  59. data/data/butler/plugins/public/help.rb +69 -0
  60. data/data/butler/plugins/public/login.rb +72 -0
  61. data/data/butler/plugins/public/usage.rb +49 -0
  62. data/data/butler/plugins/service/clones.rb +56 -0
  63. data/data/butler/plugins/service/define.rb +47 -0
  64. data/data/butler/plugins/service/log.rb +183 -0
  65. data/data/butler/plugins/service/svn.rb +91 -0
  66. data/data/butler/plugins/util/cycle.rb +98 -0
  67. data/data/butler/plugins/util/load.rb +41 -0
  68. data/data/butler/plugins/util/pong.rb +29 -0
  69. data/data/butler/strings/random/acknowledge.en.yaml +5 -0
  70. data/data/butler/strings/random/gratitude.en.yaml +3 -0
  71. data/data/butler/strings/random/hello.en.yaml +4 -0
  72. data/data/butler/strings/random/ignorance.en.yaml +7 -0
  73. data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
  74. data/data/butler/strings/random/insult.en.yaml +3 -0
  75. data/data/butler/strings/random/rejection.en.yaml +12 -0
  76. data/data/man/botcontrol.1 +17 -0
  77. data/lib/access.rb +187 -0
  78. data/lib/access/admin.rb +16 -0
  79. data/lib/access/privilege.rb +122 -0
  80. data/lib/access/role.rb +102 -0
  81. data/lib/access/savable.rb +18 -0
  82. data/lib/access/user.rb +180 -0
  83. data/lib/access/yamlbase.rb +126 -0
  84. data/lib/butler.rb +188 -0
  85. data/lib/butler/bot.rb +247 -0
  86. data/lib/butler/control.rb +93 -0
  87. data/lib/butler/dialog.rb +64 -0
  88. data/lib/butler/initialvalues.rb +40 -0
  89. data/lib/butler/irc/channel.rb +135 -0
  90. data/lib/butler/irc/channels.rb +96 -0
  91. data/lib/butler/irc/client.rb +351 -0
  92. data/lib/butler/irc/hostmask.rb +53 -0
  93. data/lib/butler/irc/message.rb +184 -0
  94. data/lib/butler/irc/parser.rb +125 -0
  95. data/lib/butler/irc/parser/commands.rb +83 -0
  96. data/lib/butler/irc/parser/generic.rb +343 -0
  97. data/lib/butler/irc/socket.rb +378 -0
  98. data/lib/butler/irc/string.rb +186 -0
  99. data/lib/butler/irc/topic.rb +15 -0
  100. data/lib/butler/irc/user.rb +265 -0
  101. data/lib/butler/irc/users.rb +112 -0
  102. data/lib/butler/plugin.rb +249 -0
  103. data/lib/butler/plugin/configproxy.rb +35 -0
  104. data/lib/butler/plugin/mapper.rb +85 -0
  105. data/lib/butler/plugin/matcher.rb +55 -0
  106. data/lib/butler/plugin/onhandlers.rb +70 -0
  107. data/lib/butler/plugin/trigger.rb +58 -0
  108. data/lib/butler/plugins.rb +147 -0
  109. data/lib/butler/version.rb +17 -0
  110. data/lib/cloptions.rb +217 -0
  111. data/lib/cloptions/adapters.rb +24 -0
  112. data/lib/cloptions/switch.rb +132 -0
  113. data/lib/configuration.rb +223 -0
  114. data/lib/dialogline.rb +296 -0
  115. data/lib/dialogline/localizations.rb +24 -0
  116. data/lib/durations.rb +57 -0
  117. data/lib/event.rb +295 -0
  118. data/lib/event/at.rb +64 -0
  119. data/lib/event/every.rb +56 -0
  120. data/lib/event/timed.rb +112 -0
  121. data/lib/installer.rb +75 -0
  122. data/lib/iterator.rb +34 -0
  123. data/lib/log.rb +68 -0
  124. data/lib/log/comfort.rb +85 -0
  125. data/lib/log/converter.rb +23 -0
  126. data/lib/log/entry.rb +152 -0
  127. data/lib/log/fakeio.rb +55 -0
  128. data/lib/log/file.rb +54 -0
  129. data/lib/log/filereader.rb +81 -0
  130. data/lib/log/forward.rb +49 -0
  131. data/lib/log/methods.rb +39 -0
  132. data/lib/log/nolog.rb +18 -0
  133. data/lib/log/splitter.rb +26 -0
  134. data/lib/ostructfixed.rb +26 -0
  135. data/lib/ruby/array/columnize.rb +38 -0
  136. data/lib/ruby/dir/mktree.rb +28 -0
  137. data/lib/ruby/enumerable/join.rb +13 -0
  138. data/lib/ruby/exception/detailed.rb +24 -0
  139. data/lib/ruby/file/append.rb +11 -0
  140. data/lib/ruby/file/write.rb +11 -0
  141. data/lib/ruby/hash/zip.rb +15 -0
  142. data/lib/ruby/kernel/bench.rb +15 -0
  143. data/lib/ruby/kernel/daemonize.rb +42 -0
  144. data/lib/ruby/kernel/non_verbose.rb +17 -0
  145. data/lib/ruby/kernel/safe_fork.rb +18 -0
  146. data/lib/ruby/range/stepped.rb +11 -0
  147. data/lib/ruby/string/arguments.rb +72 -0
  148. data/lib/ruby/string/chunks.rb +15 -0
  149. data/lib/ruby/string/post_arguments.rb +44 -0
  150. data/lib/ruby/string/unescaped.rb +17 -0
  151. data/lib/scheduler.rb +164 -0
  152. data/lib/scriptfile.rb +101 -0
  153. data/lib/templater.rb +86 -0
  154. data/test/cloptions.rb +134 -0
  155. data/test/cv.rb +28 -0
  156. data/test/irc/client.rb +85 -0
  157. data/test/irc/client_login.txt +53 -0
  158. data/test/irc/client_subscribe.txt +8 -0
  159. data/test/irc/message.rb +30 -0
  160. data/test/irc/messages.txt +64 -0
  161. data/test/irc/parser.rb +13 -0
  162. data/test/irc/profile_parser.rb +12 -0
  163. data/test/irc/users.rb +28 -0
  164. metadata +256 -0
@@ -0,0 +1,98 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ extend OnHandlers
10
+
11
+ configuration(
12
+ "channels" => {}
13
+ )
14
+
15
+ trigger "cycle"
16
+
17
+ def on_part(listener, user, channel)
18
+ if (
19
+ user != butler.myself && # don't trigger if butler itself parts
20
+ channel.length == 1 && # trigger only if butler is the last remaining
21
+ plugin.config["channels"][channel.to_str] && # trigger only if butler is configured to cycle this channel
22
+ !@butler.myself.op?(channel.to_str) # trigger only if butler isn't op already
23
+ ) then
24
+ @butler.irc.part("Cycling", channel)
25
+ @butler.irc.join(channel)
26
+ end
27
+ end
28
+
29
+ def on_trigger
30
+ case arguments(1)
31
+ when nil
32
+ usage
33
+ when _(:list)
34
+ answer(:cycling, :channels => plugin.config["channels"].keys)
35
+
36
+ when _(:on)
37
+ channels, invalid = @message.arguments[2..-1].partition { |channel|
38
+ channel.valid_channelname?
39
+ }
40
+ chanhash = plugin.config["channels"]
41
+ channels.each { |channel| chanhash[channel] = true }
42
+ plugin.config["channels"] = plugin.config["channels"].merge(chanhash)
43
+ answer(:invalid, :channels => invalid) unless invalid.empty?
44
+ answer(:activated, :channels => channels) unless channels.empty?
45
+
46
+ when _(:off)
47
+ channels, unlisted = @message.arguments[2..-1].partition { |channel|
48
+ plugin.config["channels"][channel]
49
+ }
50
+ chanhash = plugin.config["channels"]
51
+ channels.each { |channel| chanhash.delete(channel) }
52
+ plugin.config["channels"] = plugin.config["channels"].merge(chanhash)
53
+ answer(:unlisted, :channels => unlisted) unless unlisted.empty?
54
+ answer(:deactivated, :channels => channels) unless channels.empty?
55
+ end
56
+ end
57
+
58
+ __END__
59
+ ---
60
+ :revision:
61
+ :configuration: 1
62
+ :summary:
63
+ en: |
64
+ Cycling lets butler part a channel and rejoin to gain op.
65
+ :about:
66
+ :mail: "apeiros@gmx.net"
67
+ :version: "1.0.0"
68
+ :author: "Stefan Rusterholz"
69
+ :strings:
70
+ :on:
71
+ en: "on"
72
+ :off:
73
+ en: "off"
74
+ :list:
75
+ en: "list"
76
+ :cycling:
77
+ en: |
78
+ Currently cycling in <% if channels.empty? then %>no channel<% else %><%= channels.join(', ') %><% end %>.
79
+ :activated:
80
+ en: |
81
+ Activated cycling for <%= channels.join(', ') %>.
82
+ :deactivated:
83
+ en: |
84
+ Deactivated cycling for <%= channels.join(', ') %>.
85
+ :invalid:
86
+ en: |
87
+ Couldn't activate cycling for <%= channels.join(', ') %>. Didn't recognize them as valid channel-names.
88
+ :unlisted:
89
+ en: |
90
+ The channels <%= channels.join(', ') %> weren't listed for cycling.
91
+ :usage:
92
+ en: |
93
+ ![b]cycle![o] ('list' | ('on' | 'off') ![c(green)]channel![o] ...)
94
+ :help:
95
+ en:
96
+ "": |
97
+ Use 'on' to activate cycling in a channel, 'off' to deactivate and 'list'
98
+ to list channels currently being cycled.
@@ -0,0 +1,41 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ trigger "load"
10
+
11
+ def on_trigger
12
+ ps_out = `ps -o vsz,rss,%cpu,%mem -p #{Process.pid}`
13
+ vsz, rss, cpu, pmem = ps_out.scan(/\d+(?:\.\d+)?/).map { |e| e.to_f }
14
+ virtual, real = (vsz-rss).div(1024), rss.div(1024)
15
+ answer(:memusage, :real => real, :virtual => virtual, :cpu => cpu, :pmem => pmem)
16
+ rescue Exception => e
17
+ answer(:failure, :exception => e)
18
+ end
19
+
20
+ __END__
21
+ ---
22
+ :revision:
23
+ :plugin: 1
24
+ :summary:
25
+ en: Displays the memory and CPU load butler produces
26
+ :about:
27
+ :mail: "apeiros@gmx.net"
28
+ :version: "1.0.0"
29
+ :author: "Stefan Rusterholz"
30
+ :usage:
31
+ en: "![b]load![o]"
32
+ :strings:
33
+ :memusage:
34
+ en: |
35
+ Usage: <%= real %>MB, <%= virtual %>MB (real/virtual), <%= cpu %>% CPU, <%= pmem %>% Memory.
36
+ :failure:
37
+ en: |
38
+ Failed with exception <%= exception %>.
39
+ :help:
40
+ en:
41
+ "": Just type
@@ -0,0 +1,29 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ trigger "ping"
10
+
11
+ def on_trigger
12
+ message.answer("pong")
13
+ end
14
+
15
+ __END__
16
+ ---
17
+ :revision:
18
+ :plugin: 1
19
+ :summary:
20
+ en: The ever popular ping-pong
21
+ :about:
22
+ :mail: "apeiros@gmx.net"
23
+ :version: "1.0.0"
24
+ :author: "Stefan Rusterholz"
25
+ :usage:
26
+ en: "![b]ping![o]"
27
+ :help:
28
+ en:
29
+ "": If you're looking for a purpose - look elsewhere...
@@ -0,0 +1,5 @@
1
+ ---
2
+ - "Ok"
3
+ - "Will do so %s"
4
+ - "Aye sir"
5
+ - "Done as asked"
@@ -0,0 +1,3 @@
1
+ ---
2
+ - "Thank you very much"
3
+ - "Thank you %s"
@@ -0,0 +1,4 @@
1
+ ---
2
+ - "hi %s"
3
+ - "heyo %s"
4
+
@@ -0,0 +1,7 @@
1
+ ---
2
+ - "\x001ACTION shruggs\x001"
3
+ - "dunno"
4
+ - "no idea"
5
+ - "no clue"
6
+ - "... eh?"
7
+ - "Don't ask me."
@@ -0,0 +1,3 @@
1
+ ---
2
+ - "I don't know about %s"
3
+ - "What is %s?"
@@ -0,0 +1,3 @@
1
+ ---
2
+ - "%s: wanker!"
3
+ - "%s, you're so stupid you can't find your ass with both hands"
@@ -0,0 +1,12 @@
1
+ ---
2
+ - "Nope"
3
+ - "Sorry, I won't do that"
4
+ - "That won't work"
5
+ - "And how could that be done?"
6
+ - "No Sir"
7
+ - "No way I'd do that"
8
+ - "Aw come on, why should I do that?"
9
+ - "Never, read me, NEVER!"
10
+ - "Dude, what the heck makes you think that's my job?"
11
+ - "Go, bother someone else with that boring task."
12
+ - "Naaa, I don't want to."
@@ -0,0 +1,17 @@
1
+ .TH botcontrol 1 "October 2007" "" "User Manuals"
2
+ .SH NAME
3
+ botcontrol - control app for butler, the IRC-bot with class
4
+ .SH SYNOPSIS
5
+ .B botcontrol
6
+ (create|start|stop|delete) botname
7
+ .SH DESCRIPTION
8
+ .B botcontrol
9
+ controls instances of butler, an IRC-bot. It allows you to create, configure, start, stop and delete them.
10
+ .SH OPTIONS
11
+ .SH FILES
12
+ .SH DIAGNOSTICS
13
+ .SH BUGS
14
+ Interactive mode is not yet implemented.
15
+ .SH AUTHOR
16
+ Stefan Rusterholz <apeiros@gmx.net>
17
+ .SH SEE ALSO
@@ -0,0 +1,187 @@
1
+ #--
2
+ # Copyright 2007 by Stefan Rusterholz.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+
8
+
9
+ require 'digest/md5'
10
+ require 'access/yamlbase'
11
+ require 'access/user'
12
+ require 'access/role'
13
+ require 'access/privilege'
14
+
15
+
16
+
17
+ class Access
18
+ # =Description
19
+ # Provides methods to create a user or authenticate
20
+ # an existing.
21
+ # Also is the bridge between Access::User and Access::Role.
22
+ # Access::User's should be tied to Access::Framework.
23
+ #
24
+ # =Synopsis
25
+ # access = Access.new(
26
+ # Access::YAMLBase.new(Access::User::Base, "./access/user"),
27
+ # Access::YAMLBase.new(Access::Role::Base, "./access/role"),
28
+ # Access::YAMLBase.new(Access::Privilege::Base, "./access/privilege")
29
+ # )
30
+ # %w(news news/create news/edit news/delete).each { |privilege|
31
+ # access.privilege.create(privilege, "...description...")
32
+ # }
33
+ # { 'newseditor' => %w(news), 'proofreader' => %w(news/edit) }.each { |role, privileges|
34
+ # access.role.create(role, "...description...", privileges)
35
+ # }
36
+ # testuser = access.user.create("test", "pass")
37
+ # testuser.activate # inactive users may neither login nor are authorized for anything
38
+ # testuser.roles.add('proofreader')
39
+ # testuser.privileges.add('news/delete')
40
+ # testuser.privileged?('news/edit') # => true
41
+ # testuser.privileged?('news/create') # => false
42
+ # testuser.authorized?('news/edit') # => false # not logged in
43
+ # testuser.authorized?('news/create') # => false
44
+ # user = access.login?('test', 'pass')
45
+ # user.privileged?('news/edit') # => true
46
+ # user.privileged?('news/create') # => false
47
+ # user.authorized?('news/edit') # => true # only users created via Access#login are authorized
48
+ # user.authorized?('news/create') # => false
49
+ #
50
+ attr_reader :user
51
+ attr_reader :role
52
+ attr_reader :privilege
53
+ attr_accessor :default_user
54
+
55
+ def initialize(user, role, privilege)
56
+ @user = user
57
+ @role = role
58
+ @privilege = privilege
59
+ [@user, @role, @privilege].each { |base|
60
+ base.access = self
61
+ }
62
+ end
63
+
64
+ def [](user_id)
65
+ @user[user_id]
66
+ end
67
+
68
+ # returns an Access::User if credentials have been correct.
69
+ def login(user_id, credentials)
70
+ return nil unless user = @user[user_id]
71
+ return nil unless correct_credentials?(user.credentials, credentials, user_id)
72
+ user.login
73
+ user
74
+ end
75
+
76
+ # Validate non-encrypted credentials against stored encrypted credentials
77
+ def correct_credentials?(stored, credentials, user_id)
78
+ return hash_credentials(credentials, user_id) == stored
79
+ end
80
+
81
+ # One-way encrypt the credentials. Currently MD5 is used
82
+ def hash_credentials(credentials, user_id)
83
+ Digest::MD5.hexdigest(credentials+user_id.downcase).upcase
84
+ end
85
+ end
86
+
87
+ if __FILE__ == $0 then
88
+ require 'fileutils'
89
+ require 'test/unit'
90
+ class TestAccess < Test::Unit::TestCase
91
+ TestDir = "test_access"
92
+ def setup
93
+ #raise "#{TestDir} already exists, aborting test" if File.exist?(TestDir)
94
+ _teardown if File.exist?(TestDir)
95
+ Dir.mkdir(TestDir)
96
+ Dir.mkdir("#{TestDir}/user")
97
+ Dir.mkdir("#{TestDir}/role")
98
+ Dir.mkdir("#{TestDir}/privilege")
99
+ end
100
+
101
+ def _teardown
102
+ FileUtils.rm_r(TestDir)
103
+ end
104
+
105
+ def test_all
106
+ access = Access.new(
107
+ Access::YAMLBase.new(Access::User::Base, "#{TestDir}/user"),
108
+ Access::YAMLBase.new(Access::Role::Base, "#{TestDir}/role"),
109
+ Access::YAMLBase.new(Access::Privilege::Base, "#{TestDir}/privilege")
110
+ #:channel => Access::YAMLBase.new("#{TestDir}/channel", Access::Location)
111
+ )
112
+ assert(!access.user.exists?("test"))
113
+ access.user.create("test", "pass")
114
+ assert(access.user.exists?("test"))
115
+
116
+ %w(news news/read news/create news/edit news/delete banners banners/statistics
117
+ statistics statistics/web statistics/ftp paid_content).each { |priv|
118
+ assert(!access.privilege.exists?(priv))
119
+ access.privilege.create(priv, "test-privilege #{priv}")
120
+ assert(access.privilege.exists?(priv))
121
+ }
122
+
123
+ access.role.create("editor", "testrole description", %w(news/create news/edit statistics))
124
+ access.role.create("chiefeditor", "testrole description 2", %w(news), %w(editor))
125
+
126
+ user = access.login("test", "pass")
127
+ assert(user)
128
+ assert(user.logged?)
129
+ assert(!user.active?)
130
+ assert(user.inactive?)
131
+ user.active = true
132
+ assert(user.active?)
133
+ assert(!user.inactive?)
134
+ user = access.user["test", true]
135
+ assert(user)
136
+ assert(user.active?)
137
+ assert(!user.inactive?)
138
+ assert(!user.logged?)
139
+
140
+ assert(!user.privileged?("foo"))
141
+ assert(!user.privileged?("foo/bar"))
142
+ assert(!user.privileged?("foo/baz"))
143
+ assert(!user.privileged?("bar"))
144
+ assert(!user.privileged?("bar/foo"))
145
+ assert(!user.privileged?("baz"))
146
+
147
+ assert(!user.authorized?("foo"))
148
+ assert(!user.authorized?("foo/bar"))
149
+ assert(!user.authorized?("foo/baz"))
150
+ assert(!user.authorized?("bar"))
151
+ assert(!user.authorized?("bar/foo"))
152
+ assert(!user.authorized?("baz"))
153
+
154
+ user.privileges.add(%w(banners statistics/web paid_content))
155
+
156
+ assert(user.privileged?("banners"))
157
+ assert(user.privileged?("banners/statistics"))
158
+ assert(user.privileged?("statistics/web"))
159
+ assert(!user.privileged?("statistics"))
160
+ assert(user.privileged?("bar/foo"))
161
+ assert(user.privileged?("baz"))
162
+
163
+ assert(!user.authorized?("foo"))
164
+ assert(!user.authorized?("foo/bar"))
165
+ assert(!user.authorized?("foo/baz"))
166
+ assert(!user.authorized?("bar"))
167
+ assert(!user.authorized?("bar/foo"))
168
+ assert(!user.authorized?("baz"))
169
+
170
+ user = access.login("test", "pass")
171
+
172
+ assert(user.privileged?("foo"))
173
+ assert(user.privileged?("foo/bar"))
174
+ assert(user.privileged?("foo/baz"))
175
+ assert(!user.privileged?("bar"))
176
+ assert(user.privileged?("bar/foo"))
177
+ assert(user.privileged?("baz"))
178
+
179
+ assert(user.authorized?("foo"))
180
+ assert(user.authorized?("foo/bar"))
181
+ assert(user.authorized?("foo/baz"))
182
+ assert(!user.authorized?("bar"))
183
+ assert(user.authorized?("bar/foo"))
184
+ assert(user.authorized?("baz"))
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,16 @@
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 Admin
11
+ # admins have all privileges
12
+ def privileged?(*args)
13
+ true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,122 @@
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
+ require 'access/savable'
11
+
12
+
13
+
14
+ class Access
15
+ class Privilege
16
+ include Savable
17
+
18
+ module Base
19
+ # Create a new Privilege
20
+ def create(privilege, description=nil)
21
+ raise "Privilege #{privilege} already exists" if exists?(privilege)
22
+ privilege = Privilege.new(privilege, description)
23
+ privilege.access = access
24
+ privilege.base = self
25
+ add(privilege)
26
+ privilege
27
+ end
28
+
29
+ # Restore an Access::Privilege from it's storable data
30
+ def load(*args) # :nodoc:
31
+ return nil unless data = super
32
+ privilege = new(*data.values_at(:id, :description))
33
+ privilege.access = access
34
+ privilege.base = self
35
+ privilege
36
+ end
37
+ end
38
+
39
+ attr_reader :id
40
+ attr_reader :description
41
+
42
+ def initialize(privilege, description=nil)
43
+ @id = privilege
44
+ @description = description || "No description"
45
+ end
46
+
47
+ def storable
48
+ {
49
+ :id => @id,
50
+ :description => @description,
51
+ }
52
+ end
53
+
54
+ def eql?(other)
55
+ self.class == other.class && @id.eql?(other.id)
56
+ end
57
+
58
+ def hash
59
+ @id.hash
60
+ end
61
+ end
62
+
63
+ class Privileges
64
+ include Enumerable
65
+
66
+ def initialize(owner, privileges=nil)
67
+ @owner = owner
68
+ @privileges = Hash.new { |h,k| h[k] = [] }.merge(privileges || {})
69
+ end
70
+
71
+ def storable
72
+ @privileges
73
+ end
74
+
75
+ def allow?(privilege, condition=nil)
76
+ walk(privilege) { |priv|
77
+ @privileges.has_key?(priv)
78
+ }
79
+ end
80
+
81
+ def add(privilege)
82
+ case privilege
83
+ when Array: privilege.each { |priv| @privileges[priv] = nil }
84
+ when Hash: privilege.each { |priv, cond| @privileges[priv] << cond }
85
+ else @privileges[privilege] = nil
86
+ end
87
+ @owner.save
88
+ end
89
+
90
+ def delete(privilege)
91
+ case privilege
92
+ when Array
93
+ privilege.each { |priv| @privileges.delete(priv) }
94
+ when Hash
95
+ privilege.each { |priv, cond|
96
+ @privileges[priv].delete(cond)
97
+ @privileges.delete(priv) if @privileges[priv].empty?
98
+ }
99
+ else
100
+ @privileges.delete(priv)
101
+ end
102
+ @owner.save
103
+ end
104
+
105
+ def list
106
+ @privileges
107
+ end
108
+
109
+ def each(&block)
110
+ @privileges.each(&block)
111
+ end
112
+
113
+ def walk(priv)
114
+ until priv.empty?
115
+ return true if yield(priv)
116
+ priv = priv.gsub(%r{(?:/|^)[^/]*$}, '')
117
+ end
118
+ return true if yield("")
119
+ false
120
+ end
121
+ end
122
+ end