5 class AuthModule < CoreBotModule
\r
9 load_array(:default, true)
\r
10 debug "initialized auth. Botusers: #{@bot.auth.save_array.inspect}"
\r
17 def save_array(key=:default)
\r
18 if @bot.auth.changed?
\r
19 @registry[key] = @bot.auth.save_array
\r
20 @bot.auth.reset_changed
\r
21 debug "saved botusers (#{key}): #{@registry[key].inspect}"
\r
25 def load_array(key=:default, forced=false)
\r
26 debug "loading botusers (#{key}): #{@registry[key].inspect}"
\r
27 @bot.auth.load_array(@registry[key], forced) if @registry.has_key?(key)
\r
30 # The permission parameters accept arguments with the following syntax:
\r
31 # cmd_path... [on #chan .... | in here | in private]
\r
32 # This auxiliary method scans the array _ar_ to see if it matches
\r
33 # the given syntax: it expects + or - signs in front of _cmd_path_
\r
34 # elements when _setting_ = true
\r
36 # It returns an array whose first element is the array of cmd_path,
\r
37 # the second element is an array of locations and third an array of
\r
38 # warnings occurred while parsing the strings
\r
40 def parse_args(ar, setting)
\r
45 next_must_be_chan = false
\r
48 ar.each_with_index { |x, i|
\r
49 if doing_cmds # parse cmd_path
\r
50 # check if the list is done
\r
51 if x == "on" or x == "in"
\r
53 next_must_be_chan = true if x == "on"
\r
56 if "+-".include?(x[0])
\r
57 warns << ArgumentError.new("please do not use + or - in front of command #{x} when resetting") unless setting
\r
59 warns << ArgumentError.new("+ or - expected in front of #{x}") if setting
\r
62 else # parse locations
\r
68 case next_must_be_chan
\r
70 locs << x.gsub(/^here$/,'_').gsub(/^private$/,'?')
\r
72 warns << ArgumentError("#{x} doesn't look like a channel name") unless @bot.server.supports[:chantypes].include?(x[0])
\r
81 warns << "trailing comma" if want_more
\r
82 warns << "you probably forgot a comma" unless last_idx == ar.length - 1
\r
83 return cmds, locs, warns
\r
86 def auth_edit_perm(m, params)
\r
88 setting = m.message.split[1] == "set"
\r
89 splits = params[:args]
\r
91 has_for = splits[-2] == "for"
\r
92 return usage(m) unless has_for
\r
95 user = @bot.auth.get_botuser(splits[-1].sub(/^all$/,"everyone"))
\r
97 return m.reply("couldn't find botuser #{splits[-1]}")
\r
99 return m.reply("you can't change permissions for #{user.username}") if user == @bot.auth.botowner
\r
100 splits.slice!(-2,2) if has_for
\r
102 cmds, locs, warns = parse_args(splits, setting)
\r
103 errs = warns.select { |w| w.kind_of?(Exception) }
\r
106 m.reply "couldn't satisfy your request: #{errs.join(',')}"
\r
117 ch = "?" if loc == "_"
\r
119 ch = m.target.to_s if loc == "_"
\r
121 cmds.each { |setval|
\r
123 val = setval[0].chr == '+'
\r
124 cmd = setval[1..-1]
\r
125 user.set_permission(cmd, val, ch)
\r
128 user.reset_permission(cmd, ch)
\r
133 m.reply "something went wrong while trying to set the permissions"
\r
136 @bot.auth.set_changed
\r
137 debug "user #{user} permissions changed"
\r
141 def auth_view_perm(m, params)
\r
143 if params[:user].nil?
\r
144 user = get_botusername_for(m.source)
\r
145 return m.reply("you are owner, you can do anything") if user == @bot.auth.botwoner
\r
147 user = @bot.auth.get_botuser(params[:user].sub(/^all$/,"everyone"))
\r
148 return m.reply("owner can do anything") if user.username == "owner"
\r
151 return m.reply("couldn't find botuser #{params[:user]}")
\r
155 perm.each { |k, val|
\r
156 next if val.perm.empty?
\r
159 str << "on any channel: "
\r
161 str << "in private: "
\r
166 val.perm.each { |cmd, bool|
\r
167 sub << (bool ? "+" : "-")
\r
168 sub.last << cmd.to_s
\r
170 str.last << sub.join(', ')
\r
173 m.reply "no permissions set for #{user.username}"
\r
175 m.reply "permissions for #{user.username}:: #{str.join('; ')}"
\r
179 def get_botuser_for(user)
\r
180 @bot.auth.irc_to_botuser(user)
\r
183 def get_botusername_for(user)
\r
184 get_botuser_for(user).username
\r
188 "welcome, #{get_botusername_for(user)}"
\r
191 def auth_auth(m, params)
\r
192 params[:botuser] = 'owner'
\r
193 auth_login(m,params)
\r
196 def auth_login(m, params)
\r
198 case @bot.auth.login(m.source, params[:botuser], params[:password])
\r
200 m.reply welcome(m.source)
\r
201 @bot.auth.set_changed
\r
203 m.reply "sorry, can't do"
\r
206 m.reply "couldn't login: #{e}"
\r
211 def auth_autologin(m, params)
\r
212 u = do_autologin(m.source)
\r
215 m.reply "I couldn't find anything to let you login automatically"
\r
217 m.reply welcome(m.source)
\r
221 def do_autologin(user)
\r
222 @bot.auth.autologin(user)
\r
225 def auth_whoami(m, params)
\r
228 # rep << m.source.nick << ", "
\r
231 rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")
\r
235 def help(cmd, topic="")
\r
238 return "login [<botuser>] [<pass>]: logs in to the bot as botuser <botuser> with password <pass>. When using the full form, you must contact the bot in private. <pass> can be omitted if <botuser> allows login-by-mask and your netmask is among the known ones. if <botuser> is omitted too autologin will be attempted"
\r
240 return "whoami: names the botuser you're linked to"
\r
244 return "a permission is specified as module::path::to::cmd; when you want to enable it, prefix it with +; when you want to disable it, prefix it with -; when using the +reset+ command, do not use any prefix"
\r
245 when "set", "reset", "[re]set", "(re)set"
\r
246 return "permissions [re]set <permission> [in <channel>] for <user>: sets or resets the permissions for botuser <user> in channel <channel> (use ? to change the permissions for private addressing)"
\r
248 return "permissions view [for <user>]: display the permissions for user <user>"
\r
250 return "topics: syntax, (re)set, view"
\r
255 return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"
\r
256 when /^(en|dis)able/
\r
257 return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"
\r
259 return "user set password <blah> : sets the user password to <blah>; passwords can only contain upper and lowercase letters and numbers, and must be at least 4 characters long"
\r
261 return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"
\r
263 return "user reset <what> : resets <what> to the default values. <what> can be +netmasks+ (the list will be emptied), +autologin+ or +login-by-mask+ (will be reset to the default value) or +password+ (a new one will be generated and you'll be told in private)"
\r
265 return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"
\r
267 return "user create <name> <password> : create botuser named <name> with password <password>. The password can be omitted, in which case a random one will be generated. The <name> should only contain alphanumeric characters and the underscore (_)"
\r
269 return "user list : lists all the botusers"
\r
271 return "user destroy <botuser> <password> : destroys <botuser>; this function #{Bold}must#{Bold} be called in two steps. On the first call, no password must be specified: <botuser> is then queued for destruction. On the second call, you must specify the correct password for <botuser>, and it will be destroyed. If you want to cancel the destruction, issue the command +user cancel destroy <botuser>+"
\r
273 return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"
\r
276 return "#{name}: login, whoami, permission syntax, permissions [re]set, permissions view, user"
\r
281 "sorry, I need more arguments to #{cmd}"
\r
284 def not_args(cmd, *stuff)
\r
285 "I can only #{cmd} these: #{stuff.join(', ')}"
\r
288 def set_prop(botuser, prop, val)
\r
289 k = prop.to_s.gsub("-","_")
\r
290 botuser.send( (k + "=").to_sym, val)
\r
293 def reset_prop(botuser, prop)
\r
294 k = prop.to_s.gsub("-","_")
\r
295 botuser.send( ("reset_"+k).to_sym)
\r
298 def ask_bool_prop(botuser, prop)
\r
299 k = prop.to_s.gsub("-","_")
\r
300 botuser.send( (k + "?").to_sym)
\r
303 def auth_manage_user(m, params)
\r
304 splits = params[:data]
\r
307 return auth_whoami(m, params) if cmd.nil?
\r
309 botuser = get_botuser_for(m.source)
\r
310 # By default, we do stuff on the botuser the irc user is bound to
\r
313 has_for = splits[-2] == "for"
\r
314 butarget = @bot.auth.get_botuser(splits[-1]) if has_for
\r
315 return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget
\r
316 splits.slice!(-2,2) if has_for
\r
318 bools = [:autologin, :"login-by-mask"]
\r
319 can_set = [:password]
\r
320 can_addrm = [:netmasks]
\r
321 can_reset = bools + can_set + can_addrm
\r
322 can_show = can_reset + ["perms"]
\r
327 return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")
\r
333 if botuser != butarget
\r
334 return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner
\r
335 return m.reply("you can't ask for someone else's password")
\r
337 return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?
\r
338 return m.reply("the password for #{butarget.username} is #{butarget.password}")
\r
340 props = splits[1..-1]
\r
347 next if k == :password
\r
351 str.last << "not" unless ask_bool_prop(butarget, k)
\r
352 str.last << " #{k}"
\r
355 if butarget.netmasks.empty?
\r
356 str.last << "no netmasks"
\r
358 str.last << butarget.netmasks.join(", ")
\r
362 return m.reply("#{butarget.username} #{str.join('; ')}")
\r
364 when :enable, :disable
\r
365 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")
\r
366 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
368 return m.reply(need_args(cmd)) unless splits[1]
\r
371 splits[1..-1].each { |a|
\r
373 if bools.include?(arg)
\r
374 set_prop(butarget, arg, cmd.to_sym == :enable)
\r
381 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?
\r
383 m.reply "I haven't changed anything"
\r
385 @bot.auth.set_changed
\r
386 return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })
\r
390 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
391 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
393 return m.reply(need_args(cmd)) unless splits[1]
\r
394 arg = splits[1].to_sym
\r
395 return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)
\r
397 return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg
\r
398 if arg == :password && m.public?
\r
399 return m.reply("is that a joke? setting the password in public?")
\r
401 set_prop(butarget, arg, argarg)
\r
402 @bot.auth.set_changed
\r
403 auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })
\r
406 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
407 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
409 return m.reply(need_args(cmd)) unless splits[1]
\r
412 splits[1..-1].each { |a|
\r
414 if can_reset.include?(arg)
\r
415 reset_prop(butarget, arg)
\r
422 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?
\r
424 m.reply "I haven't changed anything"
\r
426 @bot.auth.set_changed
\r
427 @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")
\r
428 return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})
\r
431 when :add, :rm, :remove, :del, :delete
\r
432 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
433 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
436 if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?
\r
437 return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")
\r
440 method = cmd.to_sym == :add ? :add_netmask : :delete_netmask
\r
444 splits[2..-1].each { |mask|
\r
446 butarget.send(method, mask.to_irc_netmask(:server => @bot.server))
\r
451 m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?
\r
452 @bot.auth.set_changed
\r
453 return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })
\r
456 m.reply "sorry, I don't know how to #{m.message}"
\r
460 def auth_tell_password(m, params)
\r
461 user = params[:user]
\r
463 botuser = @bot.auth.get_botuser(params[:botuser])
\r
465 return m.reply("coudln't find botuser #{params[:botuser]})")
\r
467 m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner
\r
468 msg = "the password for botuser #{botuser.username} is #{botuser.password}"
\r
470 @bot.say m.source, "I told #{user} that " + msg
\r
473 def auth_create_user(m, params)
\r
474 name = params[:name]
\r
475 password = params[:password]
\r
476 return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?
\r
478 bu = @bot.auth.create_botuser(name, password)
\r
479 @bot.auth.set_changed
\r
481 m.reply "failed to create #{name}: #{e}"
\r
482 debug e.inspect + "\n" + e.backtrace.join("\n")
\r
485 m.reply "created botuser #{bu.username}"
\r
488 def auth_list_users(m, params)
\r
489 # TODO name regexp to filter results
\r
490 list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']
\r
491 if defined?(@destroy_q)
\r
493 @destroy_q.include?(x) ? x + " (queued for destruction)" : x
\r
496 return m.reply("I have no botusers other than the default ones") if list.empty?
\r
497 return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")
\r
500 def auth_destroy_user(m, params)
\r
501 @destroy_q = [] unless defined?(@destroy_q)
\r
502 buname = params[:name]
\r
503 return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)
\r
504 cancel = m.message.split[1] == 'cancel'
\r
505 password = params[:password]
\r
507 buser_array = @bot.auth.save_array
\r
508 buser_hash = buser_array.inject({}) { |h, u|
\r
509 h[u[:username]] = u
\r
513 return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)
\r
516 if @destroy_q.include?(buname)
\r
517 @destroy_q.delete(buname)
\r
518 m.reply "#{buname} removed from the destruction queue"
\r
520 m.reply "#{buname} was not queued for destruction"
\r
526 if @destroy_q.include?(buname)
\r
527 rep = "#{buname} already queued for destruction"
\r
529 @destroy_q << buname
\r
530 rep = "#{buname} queued for destruction"
\r
532 return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")
\r
535 return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)
\r
536 return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password
\r
537 buser_array.delete_if { |u|
\r
538 u[:username] == buname
\r
540 @destroy_q.delete(buname)
\r
541 @bot.auth.load_array(buser_array, true)
\r
542 @bot.auth.set_changed
\r
544 return m.reply("failed: #{e}")
\r
546 return m.reply("botuser #{buname} destroyed")
\r
551 def auth_copy_ren_user(m, params)
\r
552 source = Auth::BotUser.sanitize_username(params[:source])
\r
553 dest = Auth::BotUser.sanitize_username(params[:dest])
\r
554 return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4
\r
556 buser_array = @bot.auth.save_array
\r
557 buser_hash = buser_array.inject({}) { |h, u|
\r
558 h[u[:username]] = u
\r
562 return m.reply("no such botuser #{source}") unless buser_hash.keys.include?(source)
\r
563 return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)
\r
565 copying = m.message.split[1] == "copy"
\r
569 buser_hash[source].each { |k, val|
\r
573 h = buser_hash[source]
\r
575 h[:username] = dest
\r
576 buser_array << h if copying
\r
578 @bot.auth.load_array(buser_array, true)
\r
579 @bot.auth.set_changed
\r
581 return m.reply("failed: #{e}")
\r
583 return m.reply("botuser #{source} copied to #{dest}") if copying
\r
584 return m.reply("botuser #{source} renamed to #{dest}")
\r
588 def auth_export(m, params)
\r
590 exportfile = "#{@bot.botclass}/new-auth.users"
\r
592 what = params[:things]
\r
594 has_to = what[-2] == "to"
\r
596 exportfile = "#{@bot.botclass}/#{what[-1]}"
\r
602 m.reply "selecting data to export ..."
\r
604 buser_array = @bot.auth.save_array
\r
605 buser_hash = buser_array.inject({}) { |h, u|
\r
606 h[u[:username]] = u
\r
611 we_want = buser_hash
\r
613 we_want = buser_hash.delete_if { |key, val|
\r
614 not what.include?(key)
\r
618 m.reply "preparing data for export ..."
\r
621 we_want.each { |k, val|
\r
628 yaml_hash[k][kk] = []
\r
630 yaml_hash[k][kk] << {
\r
631 :fullform => nm.fullform,
\r
632 :casemap => nm.casemap.to_s
\r
636 yaml_hash[k][kk] = v
\r
641 m.reply "failed to prepare data: #{e}"
\r
642 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
646 m.reply "exporting to #{exportfile} ..."
\r
648 # m.reply yaml_hash.inspect
\r
649 File.open(exportfile, "w") do |file|
\r
650 file.puts YAML::dump(yaml_hash)
\r
653 m.reply "failed to export users: #{e}"
\r
654 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
660 def auth_import(m, params)
\r
662 importfile = "#{@bot.botclass}/new-auth.users"
\r
664 what = params[:things]
\r
666 has_from = what[-2] == "from"
\r
668 importfile = "#{@bot.botclass}/#{what[-1]}"
\r
674 m.reply "reading #{importfile} ..."
\r
676 yaml_hash = YAML::load_file(importfile)
\r
678 m.reply "failed to import from: #{e}"
\r
679 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
683 # m.reply yaml_hash.inspect
\r
685 m.reply "selecting data to import ..."
\r
688 we_want = yaml_hash
\r
690 we_want = yaml_hash.delete_if { |key, val|
\r
691 not what.include?(key)
\r
695 m.reply "parsing data from import ..."
\r
700 yaml_hash.each { |k, val|
\r
701 buser_hash[k] = { :username => k }
\r
705 buser_hash[k][kk] = []
\r
707 buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)
\r
710 buser_hash[k][kk] = v
\r
715 m.reply "failed to parse data: #{e}"
\r
716 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
720 # m.reply buser_hash.inspect
\r
722 org_buser_array = @bot.auth.save_array
\r
723 org_buser_hash = org_buser_array.inject({}) { |h, u|
\r
724 h[u[:username]] = u
\r
728 # TODO we may want to do a(n optional) key-by-key merge
\r
730 org_buser_hash.merge!(buser_hash)
\r
731 new_buser_array = org_buser_hash.values
\r
732 @bot.auth.load_array(new_buser_array, true)
\r
733 @bot.auth.set_changed
\r
740 auth = AuthModule.new
\r
742 auth.map "user export *things",
\r
743 :action => 'auth_export',
\r
744 :defaults => { :things => ['all'] },
\r
745 :auth_path => ':manage:fedex:'
\r
747 auth.map "user import *things",
\r
748 :action => 'auth_import',
\r
749 :auth_path => ':manage:fedex:'
\r
751 auth.map "user create :name :password",
\r
752 :action => 'auth_create_user',
\r
753 :defaults => {:password => nil},
\r
754 :auth_path => ':manage:'
\r
756 auth.map "user [cancel] destroy :name :password",
\r
757 :action => 'auth_destroy_user',
\r
758 :defaults => { :password => nil },
\r
759 :auth_path => ':manage::destroy:'
\r
761 auth.map "user copy :source [to] :dest",
\r
762 :action => 'auth_copy_ren_user',
\r
763 :auth_path => ':manage:'
\r
765 auth.map "user rename :source [to] :dest",
\r
766 :action => 'auth_copy_ren_user',
\r
767 :auth_path => ':manage:'
\r
769 auth.default_auth("user::manage", false)
\r
771 auth.map "user tell :user the password for :botuser",
\r
772 :action => 'auth_tell_password',
\r
775 auth.map "user list",
\r
776 :action => 'auth_list_users',
\r
779 auth.map "user *data",
\r
780 :action => 'auth_manage_user'
\r
782 auth.default_auth("user", true)
\r
783 auth.default_auth("edit::other", false)
\r
786 :action => 'auth_whoami',
\r
787 :auth_path => '!*!'
\r
789 auth.map "auth :password",
\r
790 :action => 'auth_auth',
\r
792 :auth_path => '!login!'
\r
794 auth.map "login :botuser :password",
\r
795 :action => 'auth_login',
\r
797 :defaults => { :password => nil },
\r
798 :auth_path => '!login!'
\r
800 auth.map "login :botuser",
\r
801 :action => 'auth_login',
\r
802 :auth_path => '!login!'
\r
805 :action => 'auth_autologin',
\r
806 :auth_path => '!login!'
\r
808 auth.map "permissions set *args",
\r
809 :action => 'auth_edit_perm',
\r
810 :auth_path => ':edit::set:'
\r
812 auth.map "permissions reset *args",
\r
813 :action => 'auth_edit_perm',
\r
814 :auth_path => ':edit::reset:'
\r
816 auth.map "permissions view [for :user]",
\r
817 :action => 'auth_view_perm',
\r
820 auth.default_auth('*', false)
\r