summaryrefslogtreecommitdiff
path: root/lib/rbot
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbot')
-rw-r--r--lib/rbot/core/remote.rb145
1 files changed, 120 insertions, 25 deletions
diff --git a/lib/rbot/core/remote.rb b/lib/rbot/core/remote.rb
index 56ba502d..de0ade5f 100644
--- a/lib/rbot/core/remote.rb
+++ b/lib/rbot/core/remote.rb
@@ -9,14 +9,76 @@
#
# From an idea by halorgium <rbot@spork.in>.
#
-# TODO client ID and auth
+# TODO find a way to manage session id (logging out, manually and/or
+# automatically)
#
require 'drb/drb'
module ::Irc
- # A RemoteCommand is similar to a BaiscUserMessage
+ module Auth
+
+ # We extend the BotUser class to handle remote logins
+ #
+ class BotUser
+
+ # A rather simple method to handle remote logins. Nothing special, just a
+ # password check.
+ #
+ def remote_login(password)
+ if password == @password
+ debug "remote login for #{self.inspect} succeeded"
+ return true
+ else
+ return false
+ end
+ end
+ end
+
+ # We extend the AuthManagerClass to handle remote logins
+ #
+ class AuthManagerClass
+
+ MAX_SESSION_ID = 2**128 - 1
+
+ # Creates a session id when the given password matches the given
+ # botusername
+ #
+ def remote_login(botusername, pwd)
+ @remote_users = Hash.new unless defined? @remote_users
+ n = BotUser.sanitize_username(botusername)
+ k = n.to_sym
+ raise "No such BotUser #{n}" unless include?(k)
+ bu = @allbotusers[k]
+ if bu.remote_login(pwd)
+ raise "ran out of session ids!" if @remote_users.length == MAX_SESSION_ID
+ session_id = rand(MAX_SESSION_ID)
+ while @remote_users.has_key?(session_id)
+ session_id = rand(MAX_SESSION_ID)
+ end
+ @remote_users[session_id] = bu
+ return session_id
+ end
+ return false
+ end
+
+ # Returns the botuser associated with the given session id
+ def remote_user(session_id)
+ return everyone unless session_id
+ return nil unless defined? @remote_users
+ if @remote_users.has_key?(session_id)
+ return @remote_users[session_id]
+ else
+ return nil
+ end
+ end
+ end
+
+ end
+
+
+ # A RemoteMessage is similar to a BasicUserMessage
#
class RemoteMessage
# associated bot
@@ -49,8 +111,13 @@ module ::Irc
end
end
+ # The RemoteDispatcher is a kind of MessageMapper, tuned to handle
+ # RemoteMessages
+ #
class RemoteDispatcher < MessageMapper
+ # It is initialized by passing it the bot instance
+ #
def initialize(bot)
super
end
@@ -63,8 +130,8 @@ module ::Irc
return @templates.length - 1
end
- # The map method for the RemoteDispatcher returns the index of the inserted
- # template
+ # The unmap method for the RemoteDispatcher nils the template at the given index,
+ # therefore effectively removing the mapping
#
def unmap(botmodule, handle)
tmpl = @templates[handle]
@@ -75,12 +142,19 @@ module ::Irc
end
# We redefine the handle() method from MessageMapper, taking into account
- # that @parent is a bot, and that we don't handle fallbacks
+ # that @parent is a bot, and that we don't handle fallbacks.
+ #
+ # On failure to dispatch anything, the method returns false. If dispatching
+ # is successfull, the method returns a Hash.
#
+ # Presently, the hash returned on success has only one key, :return, whose
+ # value is the actual return value of the successfull dispatch.
+ #
# TODO this same kind of mechanism could actually be used in MessageMapper
# itself to be able to handle the case of multiple plugins having the same
# 'first word' ...
#
+ #
def handle(m)
return false if @templates.empty?
failures = []
@@ -97,18 +171,17 @@ module ::Irc
failures << [tmpl, "#{botmodule} does not respond to action #{action}"]
next
end
- # TODO
- # auth = tmpl.options[:full_auth_path]
- # debug "checking auth for #{auth}"
- # if m.bot.auth.allow?(auth, m.source, m.replyto)
+ auth = tmpl.options[:full_auth_path]
+ debug "checking auth for #{auth}"
+ # We check for private permission
+ if m.bot.auth.allow?(auth, m.source, '?')
debug "template match found and auth'd: #{action.inspect} #{options.inspect}"
- botmodule.send(action, m, options)
- return true
- # end
- # debug "auth failed for #{auth}"
- # # if it's just an auth failure but otherwise the match is good,
- # # don't try any more handlers
- # return false
+ return :return => botmodule.send(action, m, options)
+ end
+ debug "auth failed for #{auth}"
+ # if it's just an auth failure but otherwise the match is good,
+ # don't try any more handlers
+ return false
end
end
failures.each {|f, r|
@@ -125,6 +198,16 @@ module ::Irc
# The Irc::IrcBot::RemoteObject class represents and object that will take care
# of interfacing with remote clients
#
+ # Example client session:
+ #
+ # require 'drb'
+ # rbot = DRbObject.new_with_uri('druby://localhost:7268')
+ # id = rbot.delegate(nil, 'remote login someuser somepass')[:return]
+ # rbot.delegate(id, 'some secret command')
+ #
+ # Of course, the remote login is only neede for commands which may not be available
+ # to everyone
+ #
class RemoteObject
# We don't want this object to be copied clientside, so we make it undumpable
@@ -137,17 +220,18 @@ module ::Irc
# The delegate method. This is the main method used by remote clients to send
# commands to the bot. Most of the time, the method will be called with only
- # two parameters (authorization code and a String), but we allow more parameters
- # for future expansions
+ # two parameters (session id and a String), but we allow more parameters
+ # for future expansions.
#
- def delegate(auth, *pars)
+ # The session_id can be nil, meaning that the remote client wants to work as
+ # an anoynomus botuser.
+ #
+ def delegate(session_id, *pars)
warn "Ignoring extra parameters" if pars.length > 1
cmd = pars.first
- # TODO implement auth <-> client conversion
- # We need at least a RemoteBotUser class derived from Irc::Auth::BotUser
- # and a way to associate the auth info to the appropriate RemoteBotUser class
- client = auth
- debug "Trying to dispatch command #{cmd.inspect} authorized by #{auth.inspect}"
+ client = @bot.auth.remote_user(session_id)
+ raise "No such session id #{session_id}" unless client
+ debug "Trying to dispatch command #{cmd.inspect} from #{client.inspect} authorized by #{session_id.inspect}"
m = RemoteMessage.new(@bot, client, cmd)
@bot.remote_dispatcher.handle(m)
end
@@ -291,6 +375,12 @@ class RemoteModule < CoreBotModule
@bot.say params[:channel], "This is a remote test"
end
+ def remote_login(m, params)
+ id = @bot.auth.remote_login(params[:botuser], params[:password])
+ raise "login failed" unless id
+ return id
+ end
+
end
remote = RemoteModule.new
@@ -303,7 +393,12 @@ remote.map "remote stop",
:action => 'handle_stop',
:auth_path => ':manage:'
+remote.default_auth('*', false)
+
remote.remote_map "remote test :channel",
:action => 'remote_test'
-remote.default_auth('*', false)
+remote.remote_map "remote login :botuser :password",
+ :action => 'remote_login'
+
+remote.default_auth('login', true)