butler 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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