diff options
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | lib/rbot/core/remote.rb | 145 |
2 files changed, 123 insertions, 25 deletions
@@ -9,6 +9,9 @@ * Remote Service Provider: BotModules can now include the RemoteBotModule interface that provides them with the remote_map() method that works just like the map() method, but for remote commands + * Remote Service Provider: Remote clients can now login remotely + before executing commands. This in fact integrates the remote access + auth security with the User/BotUser used on IRC itself. 2007-02-08 Giuseppe Bilotta <giuseppe.bilotta@gmail.com> 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) |