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.
- 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
|