\r
require 'singleton'\r
require 'set'\r
+require 'rbot/maskdb'\r
\r
# This would be a good idea if it was failproof, but the truth\r
# is that other methods can indirectly modify the hash. *sigh*\r
# class_eval {\r
# define_method(m) { |*a|\r
# r = super(*a)\r
-# Irc::Bot::Auth.authmanager.set_changed\r
+# Irc::Bot::Auth.manager.set_changed\r
# r\r
# }\r
# }\r
#\r
def permit?(str)\r
cmd = str.to_irc_auth_command\r
+ # TODO user-configurable list of always-allowed commands,\r
+ # for admins that want to set permissions -* for everybody\r
+ return true if cmd.command == :login\r
allow = nil\r
cmd.path.reverse.each { |k|\r
if @perm.has_key?(k)\r
attr_reader :password\r
attr_reader :netmasks\r
attr_reader :perm\r
- # Please remember to #set_changed() the authmanager\r
- # when modifying data\r
- attr_reader :data\r
attr_writer :login_by_mask\r
- attr_writer :autologin\r
attr_writer :transient\r
\r
+ def autologin=(vnew)\r
+ vold = @autologin\r
+ @autologin = vnew\r
+ if vold && !vnew\r
+ @netmasks.each { |n| Auth.manager.maskdb.remove(self, n) }\r
+ elsif vnew && !vold\r
+ @netmasks.each { |n| Auth.manager.maskdb.add(self, n) }\r
+ end\r
+ end\r
+\r
# Checks if the BotUser is transient\r
def transient?\r
@transient\r
\r
# Checks if the BotUser is permanent (not transient)\r
def permanent?\r
- !@permanent\r
+ !@transient\r
end\r
\r
# Sets if the BotUser is permanent or not\r
@transient=!bool\r
end\r
\r
+ # Make the BotUser permanent\r
+ def make_permanent(name)\r
+ raise TypeError, "permanent already" if permanent?\r
+ @username = BotUser.sanitize_username(name)\r
+ @transient = false\r
+ reset_autologin\r
+ reset_password # or not?\r
+ @netmasks.dup.each do |m|\r
+ delete_netmask(m)\r
+ add_netmask(m.generalize)\r
+ end\r
+ end\r
+\r
# Create a new BotUser with given username\r
def initialize(username, options={})\r
opts = {:transient => false}.merge(options)\r
raise "must provide a usable mask for transient BotUser #{@username}" if @transient and @netmasks.empty?\r
\r
@perm = {}\r
-\r
- # @data = AuthNotifyingHash.new\r
- @data = {}\r
end\r
\r
# Inspection\r
def inspect\r
- str = "<#{self.class}:#{'0x%08x' % self.object_id}"\r
+ str = self.__to_s__[0..-2]\r
str << " (transient)" if @transient\r
str << ":"\r
str << " @username=#{@username.inspect}"\r
str << " @perm=#{@perm.inspect}"\r
str << " @login_by_mask=#{@login_by_mask}"\r
str << " @autologin=#{@autologin}"\r
- if @data.empty?\r
- str << " no data"\r
- else\r
- str << " data for #{@data.keys.join(', ')}"\r
- end\r
str << ">"\r
end\r
\r
:perm => @perm,\r
:login_by_mask => @login_by_mask,\r
:autologin => @autologin,\r
- :data => @data\r
}\r
end\r
\r
# Reset the login-by-mask option\r
#\r
def reset_login_by_mask\r
- @login_by_mask = Auth.authmanager.bot.config['auth.login_by_mask'] unless defined?(@login_by_mask)\r
+ @login_by_mask = Auth.manager.bot.config['auth.login_by_mask'] unless defined?(@login_by_mask)\r
end\r
\r
# Reset the autologin option\r
#\r
def reset_autologin\r
- @autologin = Auth.authmanager.bot.config['auth.autologin'] unless defined?(@autologin)\r
+ @autologin = Auth.manager.bot.config['auth.autologin'] unless defined?(@autologin)\r
end\r
\r
# Do we allow automatic logging in?\r
def from_hash(h)\r
@username = h[:username] if h.has_key?(:username)\r
@password = h[:password] if h.has_key?(:password)\r
- @netmasks = h[:netmasks] if h.has_key?(:netmasks)\r
- @perm = h[:perm] if h.has_key?(:perm)\r
@login_by_mask = h[:login_by_mask] if h.has_key?(:login_by_mask)\r
@autologin = h[:autologin] if h.has_key?(:autologin)\r
- @data.replace(h[:data]) if h.has_key?(:data)\r
+ if h.has_key?(:netmasks)\r
+ @netmasks = h[:netmasks]\r
+ @netmasks.each { |n| Auth.manager.maskdb.add(self, n) } if @autologin\r
+ end\r
+ @perm = h[:perm] if h.has_key?(:perm)\r
end\r
\r
# This method sets the password if the proposed new password\r
# Adds a Netmask\r
#\r
def add_netmask(mask)\r
- @netmasks << mask.to_irc_netmask\r
+ m = mask.to_irc_netmask\r
+ @netmasks << m\r
+ if self.autologin?\r
+ Auth.manager.maskdb.add(self, m)\r
+ Auth.manager.logout_transients(m) if self.permanent?\r
+ end\r
end\r
\r
# Removes a Netmask\r
def delete_netmask(mask)\r
m = mask.to_irc_netmask\r
@netmasks.delete(m)\r
- end\r
-\r
- # Removes all <code>Netmask</code>s\r
- #\r
- def reset_netmasks\r
- @netmasks = NetmaskList.new\r
+ Auth.manager.maskdb.remove(self, m) if self.autologin?\r
end\r
\r
# This method checks if BotUser has a Netmask that matches _user_\r
#\r
def knows?(usr)\r
user = usr.to_irc_user\r
- known = false\r
- @netmasks.each { |n|\r
- if user.matches?(n)\r
- known = true\r
- break\r
- end\r
- }\r
- return known\r
+ !!@netmasks.find { |n| user.matches? n }\r
end\r
\r
# This method gets called when User _user_ wants to log in.\r
return true\r
end\r
\r
- # Resets the NetmaskList\r
- def reset_netmasks\r
- super\r
- add_netmask("*!*@*")\r
- end\r
-\r
# DefaultBotUser will check the default_perm after checking\r
# the global ones\r
# or on all channels if _chan_ is nil\r
end\r
\r
\r
- # This is the AuthManagerClass singleton, used to manage User/BotUser connections and\r
- # everything\r
+ class BotUser\r
+ # Check if the current BotUser is the default one\r
+ def default?\r
+ return DefaultBotUserClass === self\r
+ end\r
+\r
+ # Check if the current BotUser is the owner\r
+ def owner?\r
+ return BotOwnerClass === self\r
+ end\r
+ end\r
+\r
+\r
+ # This is the ManagerClass singleton, used to manage\r
+ # Irc::User/Irc::Bot::Auth::BotUser connections and everything\r
#\r
- class AuthManagerClass\r
+ class ManagerClass\r
\r
include Singleton\r
\r
+ attr_reader :maskdb\r
attr_reader :everyone\r
attr_reader :botowner\r
attr_reader :bot\r
# resets the hashes\r
def reset_hashes\r
@botusers = Hash.new\r
+ @maskdb = NetmaskDb.new\r
@allbotusers = Hash.new\r
- [everyone, botowner].each { |x|\r
+ [everyone, botowner].each do |x|\r
@allbotusers[x.username.to_sym] = x\r
- }\r
- @transients = Set.new\r
+ end\r
end\r
\r
def load_array(ary, forced)\r
ircuser = user.to_irc_user\r
debug "Trying to autologin #{ircuser}"\r
return @botusers[ircuser] if @botusers.has_key?(ircuser)\r
- @allbotusers.each { |n, bu|\r
- debug "Checking with #{n}"\r
- return bu if bu.autologin? and login(ircuser, n)\r
- }\r
- # Check with transient users\r
- @transients.each { |bu|\r
- return bu if bu.login(ircuser)\r
- }\r
+ bu = maskdb.find(ircuser)\r
+ if bu\r
+ debug "trying #{bu}"\r
+ bu.login(ircuser) or raise '...what?!'\r
+ @botusers[ircuser] = bu\r
+ return bu\r
+ end\r
# Finally, create a transient if we're set to allow it\r
if @bot.config['auth.autouser']\r
bu = create_transient_botuser(ircuser)\r
+ @botusers[ircuser] = bu\r
return bu\r
end\r
return everyone\r
end\r
\r
# Creates a new transient BotUser associated with Irc::User _user_,\r
- # automatically logging him in\r
+ # automatically logging him in. Note that transient botuser creation can\r
+ # fail, typically if we don't have the complete user netmask (e.g. for\r
+ # messages coming in from a linkbot)\r
#\r
def create_transient_botuser(user)\r
ircuser = user.to_irc_user\r
- bu = BotUser.new(ircuser, :transient => true, :masks => ircuser)\r
- bu.login(ircuser)\r
- @transients << bu\r
+ bu = everyone\r
+ begin\r
+ bu = BotUser.new(ircuser, :transient => true, :masks => ircuser)\r
+ bu.login(ircuser)\r
+ rescue\r
+ warning "failed to create transient for #{user}"\r
+ error $!\r
+ end\r
return bu\r
end\r
\r
+ # Logs out any Irc::User matching Irc::Netmask _m_ and logged in\r
+ # to a transient BotUser\r
+ #\r
+ def logout_transients(m)\r
+ debug "to check: #{@botusers.keys.join ' '}"\r
+ @botusers.keys.each do |iu|\r
+ debug "checking #{iu.fullform} against #{m.fullform}"\r
+ bu = @botusers[iu]\r
+ bu.transient? or next\r
+ iu.matches?(m) or next\r
+ @botusers.delete(iu).autologin = false\r
+ end\r
+ end\r
+\r
+ # Makes transient BotUser _user_ into a permanent BotUser\r
+ # named _name_; if _user_ is an Irc::User, act on the transient\r
+ # BotUser (if any) it's logged in as\r
+ #\r
+ def make_permanent(user, name)\r
+ buname = BotUser.sanitize_username(name)\r
+ # TODO merge BotUser instead?\r
+ raise "there's already a BotUser called #{name}" if include?(buname)\r
+\r
+ tuser = nil\r
+ case user\r
+ when String, Irc::User\r
+ tuser = irc_to_botuser(user)\r
+ when BotUser\r
+ tuser = user\r
+ else\r
+ raise TypeError, "sorry, don't know how to make #{user.class} into a permanent BotUser"\r
+ end\r
+ return nil unless tuser\r
+ raise TypeError, "#{tuser} is not transient" unless tuser.transient?\r
+\r
+ tuser.make_permanent(buname)\r
+ @allbotusers[tuser.username.to_sym] = tuser\r
+\r
+ return tuser\r
+ end\r
+\r
# Checks if User _user_ can do _cmd_ on _chan_.\r
#\r
# Permission are checked in this order, until a true or false\r
\r
end\r
\r
- # Returns the only instance of AuthManagerClass\r
+ # Returns the only instance of ManagerClass\r
#\r
- def Auth.authmanager\r
- return AuthManagerClass.instance\r
+ def Auth.manager\r
+ return ManagerClass.instance\r
end\r
\r
end\r
# associated with the receiver\r
#\r
def botuser\r
- Irc::Bot::Auth.authmanager.irc_to_botuser(self)\r
- end\r
-\r
- # Bot-specific data can be stored with Irc::Users. This is\r
- # internally obtained by storing data to the associated BotUser,\r
- # but this is a detail plugin writers shouldn't care about.\r
- # bot_data(:key) can be used to retrieve a particular data set.\r
- # This method is intended for data retrieval, and if the retrieved\r
- # data is modified directly there is no guarantee the changes will\r
- # be saved back. Use #set_bot_data() for that.\r
- #\r
- def bot_data(key=nil)\r
- return self.botuser.data if key.nil?\r
- return self.botuser.data[key]\r
- end\r
-\r
- # This method is used to store bot-specific data for the receiver.\r
- # If no block is passed, _value_ is stored for the key _key_;\r
- # if a block is passed, it will be called with the previous\r
- # _key_ value as parameter, and its return value will be stored\r
- # as the new value. If _value_ is present in the block form, it\r
- # will be used to initialize _key_ if it's missing\r
- # \r
- def set_bot_data(key,value=nil,&block)\r
- if not block_given?\r
- self.botuser.data[key]=value\r
- Irc::Bot::Auth.authmanager.set_changed\r
- return value\r
- end\r
- if value and not bot_data.has_key?(key)\r
- set_bot_data(key, value)\r
- end\r
- r = value\r
- begin\r
- r = yield bot_data(key)\r
- ensure\r
- Irc::Bot::Auth.authmanager.set_changed\r
- end\r
- return r\r
+ Irc::Bot::Auth.manager.irc_to_botuser(self)\r
end\r
end\r
\r