butler 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/GPL.txt +340 -0
- data/LICENSE.txt +52 -0
- data/README +37 -0
- data/Rakefile +334 -0
- data/bin/botcontrol +230 -0
- data/data/butler/config_template.yaml +4 -0
- data/data/butler/dialogs/backup.rb +19 -0
- data/data/butler/dialogs/botcontrol.rb +4 -0
- data/data/butler/dialogs/config.rb +1 -0
- data/data/butler/dialogs/create.rb +53 -0
- data/data/butler/dialogs/delete.rb +3 -0
- data/data/butler/dialogs/en/backup.yaml +6 -0
- data/data/butler/dialogs/en/botcontrol.yaml +5 -0
- data/data/butler/dialogs/en/create.yaml +11 -0
- data/data/butler/dialogs/en/delete.yaml +2 -0
- data/data/butler/dialogs/en/help.yaml +17 -0
- data/data/butler/dialogs/en/info.yaml +13 -0
- data/data/butler/dialogs/en/list.yaml +4 -0
- data/data/butler/dialogs/en/notyetimplemented.yaml +2 -0
- data/data/butler/dialogs/en/rename.yaml +3 -0
- data/data/butler/dialogs/en/start.yaml +3 -0
- data/data/butler/dialogs/en/sync_plugins.yaml +3 -0
- data/data/butler/dialogs/en/uninstall.yaml +5 -0
- data/data/butler/dialogs/en/unknown_command.yaml +2 -0
- data/data/butler/dialogs/help.rb +11 -0
- data/data/butler/dialogs/info.rb +27 -0
- data/data/butler/dialogs/interactive.rb +1 -0
- data/data/butler/dialogs/list.rb +10 -0
- data/data/butler/dialogs/notyetimplemented.rb +1 -0
- data/data/butler/dialogs/rename.rb +4 -0
- data/data/butler/dialogs/selectbot.rb +2 -0
- data/data/butler/dialogs/start.rb +5 -0
- data/data/butler/dialogs/sync_plugins.rb +30 -0
- data/data/butler/dialogs/uninstall.rb +17 -0
- data/data/butler/dialogs/unknown_command.rb +1 -0
- data/data/butler/plugins/core/logout.rb +41 -0
- data/data/butler/plugins/core/plugins.rb +134 -0
- data/data/butler/plugins/core/privilege.rb +103 -0
- data/data/butler/plugins/core/user.rb +166 -0
- data/data/butler/plugins/dev/eval.rb +64 -0
- data/data/butler/plugins/dev/nometa.rb +14 -0
- data/data/butler/plugins/dev/onhandlers.rb +93 -0
- data/data/butler/plugins/dev/raw.rb +36 -0
- data/data/butler/plugins/dev/rawlog.rb +77 -0
- data/data/butler/plugins/games/eightball.rb +54 -0
- data/data/butler/plugins/games/mastermind.rb +174 -0
- data/data/butler/plugins/irc/action.rb +36 -0
- data/data/butler/plugins/irc/join.rb +38 -0
- data/data/butler/plugins/irc/notice.rb +36 -0
- data/data/butler/plugins/irc/part.rb +38 -0
- data/data/butler/plugins/irc/privmsg.rb +36 -0
- data/data/butler/plugins/irc/quit.rb +36 -0
- data/data/butler/plugins/operator/deop.rb +41 -0
- data/data/butler/plugins/operator/devoice.rb +41 -0
- data/data/butler/plugins/operator/limit.rb +47 -0
- data/data/butler/plugins/operator/op.rb +41 -0
- data/data/butler/plugins/operator/voice.rb +41 -0
- data/data/butler/plugins/public/help.rb +69 -0
- data/data/butler/plugins/public/login.rb +72 -0
- data/data/butler/plugins/public/usage.rb +49 -0
- data/data/butler/plugins/service/clones.rb +56 -0
- data/data/butler/plugins/service/define.rb +47 -0
- data/data/butler/plugins/service/log.rb +183 -0
- data/data/butler/plugins/service/svn.rb +91 -0
- data/data/butler/plugins/util/cycle.rb +98 -0
- data/data/butler/plugins/util/load.rb +41 -0
- data/data/butler/plugins/util/pong.rb +29 -0
- data/data/butler/strings/random/acknowledge.en.yaml +5 -0
- data/data/butler/strings/random/gratitude.en.yaml +3 -0
- data/data/butler/strings/random/hello.en.yaml +4 -0
- data/data/butler/strings/random/ignorance.en.yaml +7 -0
- data/data/butler/strings/random/ignorance_about.en.yaml +3 -0
- data/data/butler/strings/random/insult.en.yaml +3 -0
- data/data/butler/strings/random/rejection.en.yaml +12 -0
- data/data/man/botcontrol.1 +17 -0
- data/lib/access.rb +187 -0
- data/lib/access/admin.rb +16 -0
- data/lib/access/privilege.rb +122 -0
- data/lib/access/role.rb +102 -0
- data/lib/access/savable.rb +18 -0
- data/lib/access/user.rb +180 -0
- data/lib/access/yamlbase.rb +126 -0
- data/lib/butler.rb +188 -0
- data/lib/butler/bot.rb +247 -0
- data/lib/butler/control.rb +93 -0
- data/lib/butler/dialog.rb +64 -0
- data/lib/butler/initialvalues.rb +40 -0
- data/lib/butler/irc/channel.rb +135 -0
- data/lib/butler/irc/channels.rb +96 -0
- data/lib/butler/irc/client.rb +351 -0
- data/lib/butler/irc/hostmask.rb +53 -0
- data/lib/butler/irc/message.rb +184 -0
- data/lib/butler/irc/parser.rb +125 -0
- data/lib/butler/irc/parser/commands.rb +83 -0
- data/lib/butler/irc/parser/generic.rb +343 -0
- data/lib/butler/irc/socket.rb +378 -0
- data/lib/butler/irc/string.rb +186 -0
- data/lib/butler/irc/topic.rb +15 -0
- data/lib/butler/irc/user.rb +265 -0
- data/lib/butler/irc/users.rb +112 -0
- data/lib/butler/plugin.rb +249 -0
- data/lib/butler/plugin/configproxy.rb +35 -0
- data/lib/butler/plugin/mapper.rb +85 -0
- data/lib/butler/plugin/matcher.rb +55 -0
- data/lib/butler/plugin/onhandlers.rb +70 -0
- data/lib/butler/plugin/trigger.rb +58 -0
- data/lib/butler/plugins.rb +147 -0
- data/lib/butler/version.rb +17 -0
- data/lib/cloptions.rb +217 -0
- data/lib/cloptions/adapters.rb +24 -0
- data/lib/cloptions/switch.rb +132 -0
- data/lib/configuration.rb +223 -0
- data/lib/dialogline.rb +296 -0
- data/lib/dialogline/localizations.rb +24 -0
- data/lib/durations.rb +57 -0
- data/lib/event.rb +295 -0
- data/lib/event/at.rb +64 -0
- data/lib/event/every.rb +56 -0
- data/lib/event/timed.rb +112 -0
- data/lib/installer.rb +75 -0
- data/lib/iterator.rb +34 -0
- data/lib/log.rb +68 -0
- data/lib/log/comfort.rb +85 -0
- data/lib/log/converter.rb +23 -0
- data/lib/log/entry.rb +152 -0
- data/lib/log/fakeio.rb +55 -0
- data/lib/log/file.rb +54 -0
- data/lib/log/filereader.rb +81 -0
- data/lib/log/forward.rb +49 -0
- data/lib/log/methods.rb +39 -0
- data/lib/log/nolog.rb +18 -0
- data/lib/log/splitter.rb +26 -0
- data/lib/ostructfixed.rb +26 -0
- data/lib/ruby/array/columnize.rb +38 -0
- data/lib/ruby/dir/mktree.rb +28 -0
- data/lib/ruby/enumerable/join.rb +13 -0
- data/lib/ruby/exception/detailed.rb +24 -0
- data/lib/ruby/file/append.rb +11 -0
- data/lib/ruby/file/write.rb +11 -0
- data/lib/ruby/hash/zip.rb +15 -0
- data/lib/ruby/kernel/bench.rb +15 -0
- data/lib/ruby/kernel/daemonize.rb +42 -0
- data/lib/ruby/kernel/non_verbose.rb +17 -0
- data/lib/ruby/kernel/safe_fork.rb +18 -0
- data/lib/ruby/range/stepped.rb +11 -0
- data/lib/ruby/string/arguments.rb +72 -0
- data/lib/ruby/string/chunks.rb +15 -0
- data/lib/ruby/string/post_arguments.rb +44 -0
- data/lib/ruby/string/unescaped.rb +17 -0
- data/lib/scheduler.rb +164 -0
- data/lib/scriptfile.rb +101 -0
- data/lib/templater.rb +86 -0
- data/test/cloptions.rb +134 -0
- data/test/cv.rb +28 -0
- data/test/irc/client.rb +85 -0
- data/test/irc/client_login.txt +53 -0
- data/test/irc/client_subscribe.txt +8 -0
- data/test/irc/message.rb +30 -0
- data/test/irc/messages.txt +64 -0
- data/test/irc/parser.rb +13 -0
- data/test/irc/profile_parser.rb +12 -0
- data/test/irc/users.rb +28 -0
- 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,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
|
data/lib/access.rb
ADDED
@@ -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
|
data/lib/access/admin.rb
ADDED
@@ -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
|