]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/commitdiff
Initial version of the new auth coremodule
authorGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Thu, 3 Aug 2006 14:07:12 +0000 (14:07 +0000)
committerGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Thu, 3 Aug 2006 14:07:12 +0000 (14:07 +0000)
lib/rbot/auth.rb [deleted file]
lib/rbot/core/auth.rb [new file with mode: 0644]

diff --git a/lib/rbot/auth.rb b/lib/rbot/auth.rb
deleted file mode 100644 (file)
index c865636..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-module Irc
-
-  # globmask:: glob to test with
-  # netmask::  netmask to test against
-  # Compare a netmask with a standard IRC glob, e.g foo!bar@baz.com would
-  # match *!*@baz.com, foo!*@*, *!bar@*, etc.
-  def Irc.netmaskmatch( globmask, netmask )
-    regmask = Regexp.escape( globmask )
-    regmask.gsub!( /\\\*/, '.*' )
-    return true if(netmask =~ /#{regmask}/i)
-    return false
-  end
-
-  # check if a string is an actual IRC hostmask
-  def Irc.ismask?(mask)
-    mask =~ /^.+!.+@.+$/
-  end
-
-  Struct.new( 'UserData', :level, :password, :hostmasks )
-
-  # User-level authentication to allow/disallow access to bot commands based
-  # on hostmask and userlevel.
-  class IrcAuth
-    BotConfig.register BotConfigStringValue.new( 'auth.password',
-      :default => 'rbotauth', :wizard => true,
-      :desc => 'Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)' )
-    BotConfig.register BotConfigIntegerValue.new( 'auth.default_level',
-      :default => 10, :wizard => true,
-      :desc => 'The default level for new/unknown users' )
-
-    # create a new IrcAuth instance.
-    # bot:: associated bot class
-    def initialize(bot)
-      @bot = bot
-      @users = Hash.new do
-        Struct::UserData.new(@bot.config['auth.default_level'], '', [])
-      end
-      @levels = Hash.new(0)
-      @currentUsers = Hash.new( nil )
-      if( File.exist?( "#{@bot.botclass}/users.yaml" ) )
-        File.open( "#{@bot.botclass}/users.yaml" ) { |file|
-          # work around YAML not maintaining the default proc
-          @loadedusers = YAML::parse(file).transform
-          @users.update(@loadedusers)
-        }
-      end
-      if(File.exist?("#{@bot.botclass}/levels.rbot"))
-        IO.foreach("#{@bot.botclass}/levels.rbot") do |line|
-          if(line =~ /\s*(\d+)\s*(\S+)/)
-            level = $1.to_i
-            command = $2
-            @levels[command] = level
-          end
-        end
-      end
-      if @levels.length < 1
-        raise RuntimeError, "No valid levels.rbot found! If you really want a free-for-all bot and this isn't the result of a previous error, write a proper levels.rbot"
-      end
-    end
-
-    # save current users and levels to files.
-    # levels are written to #{botclass}/levels.rbot
-    # users are written to #{botclass}/users.yaml
-    def save
-      Dir.mkdir("#{@bot.botclass}") if(!File.exist?("#{@bot.botclass}"))
-      begin
-        debug "Writing new users.yaml ..."
-        File.open("#{@bot.botclass}/users.yaml.new", 'w') do |file|
-          file.puts @users.to_yaml
-        end
-        debug "Officializing users.yaml ..."
-        File.rename("#{@bot.botclass}/users.yaml.new",
-                    "#{@bot.botclass}/users.yaml")
-      rescue
-        error "failed to write configuration file users.yaml! #{$!}"
-        error "#{e.class}: #{e}"
-        error e.backtrace.join("\n")
-      end
-      begin
-        debug "Writing new levels.rbot ..."
-        File.open("#{@bot.botclass}/levels.rbot.new", 'w') do |file|
-          @levels.each do |key, value|
-            file.puts "#{value} #{key}"
-          end
-        end
-        debug "Officializing levels.rbot ..."
-        File.rename("#{@bot.botclass}/levels.rbot.new",
-                    "#{@bot.botclass}/levels.rbot")
-      rescue
-        error "failed to write configuration file levels.rbot! #{$!}"
-        error "#{e.class}: #{e}"
-        error e.backtrace.join("\n")
-      end
-    end
-
-    # command:: command user wishes to perform
-    # mask::    hostmask of user
-    # tell::    optional recipient for "insufficient auth" message
-    #
-    # returns true if user with hostmask +mask+ is permitted to perform
-    # +command+ optionally pass tell as the target for the "insufficient auth"
-    # message, if the user is not authorised
-    def allow?( command, mask, tell=nil )
-      auth = @users[matchingUser(mask)].level # Directly using @users[] is possible, because UserData has a default setting
-        if( auth >= @levels[command] )
-          return true
-        else
-          debug "#{mask} is not allowed to perform #{command}"
-          @bot.say tell, "insufficient \"#{command}\" auth (have #{auth}, need #{@levels[command]})" if tell
-          return false
-        end
-    end
-
-    # add user with hostmask matching +mask+ with initial auth level +level+
-    def useradd( username, level=@bot.config['auth.default_level'], password='', hostmask='*!*@*' )
-      @users[username] = Struct::UserData.new( level, password, [hostmask] ) if ! @users.has_key? username
-    end
-
-    # mask:: mask of user to remove
-    # remove user with mask +mask+
-    def userdel( username )
-      @users.delete( username ) if @users.has_key? username
-    end
-
-    def usermod( username, item, value=nil )
-      if @users.has_key?( username )
-        case item
-          when 'hostmask'
-            if Irc.ismask?( value )
-              @users[username].hostmasks = [ value ]
-              return true
-            end
-          when '+hostmask'
-            if Irc.ismask?( value )
-              @users[username].hostmasks += [ value ]
-              return true
-            end
-          when '-hostmask'
-            if Irc.ismask?( value )
-              @users[username].hostmasks -= [ value ]
-              return true
-            end
-          when 'password'
-              @users[username].password = value
-              return true
-          when 'level'
-              @users[username].level = value.to_i
-              return true
-          else
-            debug "usermod: Tried to modify unknown item #{item}"
-           # @bot.say tell, "Unknown item #{item}" if tell
-        end
-      end
-      return false
-    end
-
-    # command:: command to adjust
-    # level::   new auth level for the command
-    # set required auth level of +command+ to +level+
-    def setlevel(command, level)
-      @levels[command] = level
-    end
-
-    def matchingUser( mask )
-      currentUser = nil
-      currentLevel = 0
-      @users.each { |user, data| # TODO Will get easier if YPaths are used...
-        if data.level > currentLevel
-          data.hostmasks.each { |hostmask|
-            if Irc.netmaskmatch( hostmask, mask )
-              currentUser = user
-              currentLevel = data.level
-            end
-          }
-        end
-      }
-      currentUser
-    end
-
-    def identify( mask, username, password )
-      return false unless @users.has_key?(username) && @users[username].password == password
-      @bot.auth.usermod( username, '+hostmask', mask )
-      return true
-    end
-
-    # return all currently defined commands (for which auth is required) and
-    # their required authlevels
-    def showlevels
-      reply = 'Current levels are:'
-      @levels.sort.each { |key, value|
-        reply += " #{key}(#{value})"
-      }
-      reply
-    end
-
-    # return all currently defined users and their authlevels
-    def showusers
-      reply = 'Current users are:'
-      @users.sort.each { |key, value|
-        reply += " #{key}(#{value.level})"
-      }
-      reply
-    end
-
-    def showdetails( username )
-      if @users.has_key? username
-        reply = "#{username}(#{@users[username].level}):"
-        @users[username].hostmasks.each { |hostmask|
-          reply += " #{hostmask}"
-        }
-      end
-      reply
-    end
-
-    # module help
-    def help(topic='')
-      case topic
-        when 'setlevel'
-          return 'setlevel <command> <level> => Sets required level for <command> to <level> (private addressing only)'
-        when 'useradd'
-          return 'useradd <username> => Add user <mask>, you still need to set him up correctly (private addressing only)'
-        when 'userdel'
-          return 'userdel <username> => Remove user <username> (private addressing only)'
-        when 'usermod'
-          return 'usermod <username> <item> <value> => Modify <username>s settings. Valid <item>s are: hostmask, (+|-)hostmask, password, level (private addressing only)'
-        when 'auth'
-          return 'auth <masterpw> => Create a user with your hostmask and master password as bot master (private addressing only)'
-        when 'levels'
-          return 'levels => list commands and their required levels (private addressing only)'
-        when 'users'
-          return 'users [<username>]=> list users and their levels or details about <username> (private addressing only)'
-        when 'whoami'
-          return 'whoami => Show as whom you are recognized (private addressing only)'
-        when 'identify'
-          return 'identify <username> <password> => Identify your hostmask as belonging to <username> (private addressing only)'
-        else
-          return 'Auth module (User authentication) topics: setlevel, useradd, userdel, usermod, auth, levels, users, whoami, identify'
-      end
-    end
-
-    # privmsg handler
-    def privmsg(m)
-     if(m.address? && m.private?)
-      case m.message
-        when (/^setlevel\s+(\S+)\s+(\d+)$/)
-          if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
-            @bot.auth.setlevel( $1, $2.to_i )
-            m.reply "level for #$1 set to #$2"
-          end
-        when( /^useradd\s+(\S+)/ ) # FIXME Needs review!!! (\s+(\S+)(\s+(\S+)(\s+(\S+))?)?)? Should this part be added to make complete useradds possible?
-          if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
-            @bot.auth.useradd( $1 )
-            m.reply "added user #$1, please set him up correctly"
-          end
-        when( /^userdel\s+(\S+)/ )
-          if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
-            @bot.auth.userdel( $1 )
-            m.reply "user #$1 is gone"
-          end
-        when( /^usermod\s+(\S+)\s+(\S+)\s+(\S+)/ )
-          if( @bot.auth.allow?('auth', m.source, m.replyto ) )
-            if( @bot.auth.usermod( $1, $2, $3 ) )
-              m.reply "Set #$2 of #$1 to #$3"
-            else
-              m.reply "Failed to set #$2 of #$1 to #$3"
-            end
-          end
-        when( /^setpassword\s+(\S+)/ )
-         password = $1
-          user = @bot.auth.matchingUser( m.source )
-          if user
-           if @bot.auth.usermod(user, 'password', password)
-             m.reply "Your password has been set to #{password}"
-           else
-             m.reply "Couldn't set password"
-           end
-          else
-            m.reply 'You don\'t belong to any user.'
-          end
-        when (/^auth\s+(\S+)/)
-          if( $1 == @bot.config['auth.password'] )
-            if ! @users.has_key? 'master'
-              @bot.auth.useradd( 'master', 1000, @bot.config['auth.password'], m.source )
-            else
-              @bot.auth.usermod( 'master', '+hostmask', m.source )
-            end
-            m.reply 'Identified, security level maxed out'
-          else
-            m.reply 'Incorrect password'
-          end
-        when( /^identify\s+(\S+)\s+(\S+)/ )
-          if @bot.auth.identify( m.source, $1, $2 )
-            m.reply "Identified as #$1 (#{@users[$1].level})"
-          else
-            m.reply 'Incorrect username/password'
-          end
-        when( 'whoami' )
-          user = @bot.auth.matchingUser( m.source )
-          if user
-            m.reply "I recognize you as #{user} (#{@users[user].level})"
-          else
-            m.reply 'You don\'t belong to any user.'
-          end
-        when( /^users\s+(\S+)/ )
-          m.reply @bot.auth.showdetails( $1 ) if( @bot.auth.allow?( 'auth', m.source, m.replyto ) )
-        when ( 'levels' )
-          m.reply @bot.auth.showlevels if( @bot.auth.allow?( 'config', m.source, m.replyto ) )
-        when ( 'users' )
-          m.reply @bot.auth.showusers if( @bot.auth.allow?( 'users', m.source, m.replyto ) )
-      end
-     end
-    end
-  end
-end
diff --git a/lib/rbot/core/auth.rb b/lib/rbot/core/auth.rb
new file mode 100644 (file)
index 0000000..9a30dc0
--- /dev/null
@@ -0,0 +1,170 @@
+#-- vim:sw=2:et\r
+#++\r
+\r
+\r
+class AuthModule < CoreBotModule\r
+\r
+  def initialize\r
+    super\r
+    load_array(:default, true)\r
+    debug "Initialized auth. Botusers: #{@bot.auth.save_array.inspect}"\r
+  end\r
+\r
+  def save\r
+    save_array\r
+  end\r
+\r
+  def save_array(key=:default)\r
+    if @bot.auth.changed?\r
+      @registry[key] = @bot.auth.save_array\r
+      @bot.auth.reset_changed\r
+      debug "saved botusers (#{key}): #{@registry[key].inspect}"\r
+    end\r
+  end\r
+\r
+  def load_array(key=:default, forced=false)\r
+    debug "loading botusers (#{key}): #{@registry[key].inspect}"\r
+    @bot.auth.load_array(@registry[key], forced) if @registry.has_key?(key)\r
+  end\r
+\r
+  # The permission parameters accept arguments with the following syntax:\r
+  #   cmd_path... [on #chan .... | in here | in private]\r
+  # This auxiliary method scans the array _ar_ to see if it matches\r
+  # the given syntax: it expects + or - signs in front of _cmd_path_\r
+  # elements when _setting_ = true\r
+  #\r
+  # It returns an array whose first element is the array of cmd_path,\r
+  # the second element is an array of locations and third an array of\r
+  # warnings occurred while parsing the strings\r
+  #\r
+  def parse_args(ar, setting)\r
+    cmds = []\r
+    locs = []\r
+    warns = []\r
+    doing_cmds = true\r
+    next_must_be_chan = false\r
+    want_more = false\r
+    last_idx = 0\r
+    ar.each_with_index { |x, i|\r
+      if doing_cmds # parse cmd_path\r
+        # check if the list is done\r
+        if x == "on" or x == "in"\r
+          doing_cmds = false\r
+          next_must_be_chan = true if x == "on"\r
+          next\r
+        end\r
+        if "+-".include?(x[0])\r
+          warns << ArgumentError("please do not use + or - in front of command #{x} when resetting") unless setting\r
+        else\r
+          warns << ArgumentError("+ or - expected in front of #{x}") if setting\r
+        end\r
+        cmds << x\r
+      else # parse locations\r
+        if x[-1].chr == ','\r
+          want_more = true\r
+        else\r
+          want_more = false\r
+        end\r
+        case next_must_be_chan\r
+        when false\r
+          locs << x.gsub(/^here$/,'_').gsub(/^private$/,'?')\r
+        else\r
+          warns << ArgumentError("#{x} doesn't look like a channel name") unless @bot.server.supports[:chantypes].include?(x[0])\r
+          locs << x\r
+        end\r
+        unless wants_more\r
+          last_idx = i\r
+          break\r
+        end\r
+      end\r
+    }\r
+    warns << "trailing comma" if wants_more\r
+    warns << "you probably forgot a comma" unless last_idx == ar.length - 1\r
+    return cmds, locs, warns\r
+  end\r
+\r
+  def auth_set(m, params)\r
+    cmds, locs, warns = parse_args(params[:args])\r
+    errs = warns.select { |w| w.class <= Exception }\r
+    unless errs.empty?\r
+      m.reply "couldn't satisfy your request: #{errs.join(',')}"\r
+      return\r
+    end\r
+    user = params[:user].sub(/^all$/,"everyone")\r
+    begin\r
+      bu = @bot.auth.get_botuser(user)\r
+    rescue\r
+      m.reply "couldn't find botuser #{user}"\r
+      return\r
+    end\r
+    if locs.empty?\r
+      locs << "*"\r
+    end\r
+    begin\r
+      locs.each { |loc|\r
+        ch = loc\r
+        if m.private?\r
+          ch = "?" if loc == "_"\r
+        else\r
+          ch = m.target.to_s if loc == "_"\r
+        end\r
+        cmds.each { |setval|\r
+          val = setval[0].chr == '+'\r
+          cmd = setval[1..-1]\r
+          bu.set_permission(cmd, val, ch)\r
+        }\r
+      }\r
+    rescue => e\r
+      m.reply "Something went wrong while trying to set the permissions"\r
+      raise\r
+    end\r
+    @bot.auth.set_changed\r
+    debug "User #{user} permissions changed"\r
+    m.reply "Ok, #{user} now also has permissions #{params[:args].join(' ')}"\r
+  end\r
+\r
+  def auth_login(m, params)\r
+    begin\r
+      if params[:password]\r
+        li = @bot.auth.login(m.source, params[:botuser], params[:password])\r
+      else\r
+        li = @bot.auth.login(m.source, params[:botuser], params[:password], true)\r
+      end\r
+      case li\r
+      when true\r
+        m.reply "welcome, #{@bot.auth.irc_to_botuser(m.source).username}"\r
+        @bot.auth.set_changed\r
+      else\r
+        m.reply "sorry, can't do"\r
+      end\r
+    rescue => e\r
+      m.reply "couldn't login: #{e}"\r
+      raise\r
+    end\r
+  end\r
+\r
+end\r
+\r
+auth = AuthModule.new\r
+\r
+auth.map "permissions set *args for :user",\r
+  :action => 'auth_set',\r
+  :auth_path => ':edit::set:'\r
+\r
+auth.map "permissions reset *args for :user",\r
+  :action => 'auth_reset',\r
+  :auth_path => ':edit::reset:'\r
+\r
+auth.map "login :botuser :password",\r
+  :action => 'auth_login',\r
+  :public => false,\r
+  :defaults => { :password => nil },\r
+  :auth_path => '!login!'\r
+\r
+auth.map "login :botuser",\r
+  :action => 'auth_login',\r
+  :defaults => { :password => nil },\r
+  :auth_path => '!login!'\r
+\r
+auth.default_auth('*', false)\r
+\r