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
253 return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"
\r
254 when /^(en|dis)able/
\r
255 return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"
\r
257 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
259 return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"
\r
261 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
263 return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"
\r
265 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
267 return "user list : lists all the botusers"
\r
269 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
271 return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"
\r
274 return "#{name}: login, whoami, permission syntax, permissions [re]set, permissions view, user"
\r
279 "sorry, I need more arguments to #{cmd}"
\r
282 def not_args(cmd, *stuff)
\r
283 "I can only #{cmd} these: #{stuff.join(', ')}"
\r
286 def set_prop(botuser, prop, val)
\r
287 k = prop.to_s.gsub("-","_")
\r
288 botuser.send( (k + "=").to_sym, val)
\r
291 def reset_prop(botuser, prop)
\r
292 k = prop.to_s.gsub("-","_")
\r
293 botuser.send( ("reset_"+k).to_sym)
\r
296 def ask_bool_prop(botuser, prop)
\r
297 k = prop.to_s.gsub("-","_")
\r
298 botuser.send( (k + "?").to_sym)
\r
301 def auth_manage_user(m, params)
\r
302 splits = params[:data]
\r
305 return auth_whoami(m, params) if cmd.nil?
\r
307 botuser = get_botuser_for(m.source)
\r
308 # By default, we do stuff on the botuser the irc user is bound to
\r
311 has_for = splits[-2] == "for"
\r
312 butarget = @bot.auth.get_botuser(splits[-1]) if has_for
\r
313 return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget
\r
314 splits.slice!(-2,2) if has_for
\r
316 bools = [:autologin, :"login-by-mask"]
\r
317 can_set = [:password]
\r
318 can_addrm = [:netmasks]
\r
319 can_reset = bools + can_set + can_addrm
\r
320 can_show = can_reset + ["perms"]
\r
325 return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")
\r
331 if botuser != butarget
\r
332 return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner
\r
333 return m.reply("you can't ask for someone else's password")
\r
335 return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?
\r
336 return m.reply("the password for #{butarget.username} is #{butarget.password}")
\r
338 props = splits[1..-1]
\r
345 next if k == :password
\r
349 str.last << "not" unless ask_bool_prop(butarget, k)
\r
350 str.last << " #{k}"
\r
353 if butarget.netmasks.empty?
\r
354 str.last << "no netmasks"
\r
356 str.last << butarget.netmasks.join(", ")
\r
360 return m.reply("#{butarget.username} #{str.join('; ')}")
\r
362 when :enable, :disable
\r
363 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")
\r
364 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
366 return m.reply(need_args(cmd)) unless splits[1]
\r
369 splits[1..-1].each { |a|
\r
371 if bools.include?(arg)
\r
372 set_prop(butarget, arg, cmd.to_sym == :enable)
\r
379 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?
\r
381 m.reply "I haven't changed anything"
\r
383 @bot.auth.set_changed
\r
384 return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })
\r
388 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
389 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
391 return m.reply(need_args(cmd)) unless splits[1]
\r
392 arg = splits[1].to_sym
\r
393 return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)
\r
395 return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg
\r
396 if arg == :password && m.public?
\r
397 return m.reply("is that a joke? setting the password in public?")
\r
399 set_prop(butarget, arg, argarg)
\r
400 @bot.auth.set_changed
\r
401 auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })
\r
404 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
405 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
407 return m.reply(need_args(cmd)) unless splits[1]
\r
410 splits[1..-1].each { |a|
\r
412 if can_reset.include?(arg)
\r
413 reset_prop(butarget, arg)
\r
420 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?
\r
422 m.reply "I haven't changed anything"
\r
424 @bot.auth.set_changed
\r
425 @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")
\r
426 return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})
\r
429 when :add, :rm, :remove, :del, :delete
\r
430 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
431 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
434 if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?
\r
435 return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")
\r
438 method = cmd.to_sym == :add ? :add_netmask : :delete_netmask
\r
442 splits[2..-1].each { |mask|
\r
444 butarget.send(method, mask.to_irc_netmask(:server => @bot.server))
\r
449 m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?
\r
450 @bot.auth.set_changed
\r
451 return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })
\r
454 m.reply "sorry, I don't know how to #{m.message}"
\r
458 def auth_tell_password(m, params)
\r
459 user = params[:user]
\r
461 botuser = @bot.auth.get_botuser(params[:botuser])
\r
463 return m.reply("coudln't find botuser #{params[:botuser]})")
\r
465 m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner
\r
466 msg = "the password for botuser #{botuser.username} is #{botuser.password}"
\r
468 @bot.say m.source, "I told #{user} that " + msg
\r
471 def auth_create_user(m, params)
\r
472 name = params[:name]
\r
473 password = params[:password]
\r
474 return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?
\r
476 bu = @bot.auth.create_botuser(name, password)
\r
477 @bot.auth.set_changed
\r
479 m.reply "failed to create #{name}: #{e}"
\r
480 debug e.inspect + "\n" + e.backtrace.join("\n")
\r
483 m.reply "created botuser #{bu.username}"
\r
486 def auth_list_users(m, params)
\r
487 # TODO name regexp to filter results
\r
488 list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']
\r
489 if defined?(@destroy_q)
\r
491 @destroy_q.include?(x) ? x + " (queued for destruction)" : x
\r
494 return m.reply("I have no botusers other than the default ones") if list.empty?
\r
495 return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")
\r
498 def auth_destroy_user(m, params)
\r
499 @destroy_q = [] unless defined?(@destroy_q)
\r
500 buname = params[:name]
\r
501 return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)
\r
502 cancel = m.message.split[1] == 'cancel'
\r
503 password = params[:password]
\r
505 buser_array = @bot.auth.save_array
\r
506 buser_hash = buser_array.inject({}) { |h, u|
\r
507 h[u[:username]] = u
\r
511 return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)
\r
514 if @destroy_q.include?(buname)
\r
515 @destroy_q.delete(buname)
\r
516 m.reply "#{buname} removed from the destruction queue"
\r
518 m.reply "#{buname} was not queued for destruction"
\r
524 if @destroy_q.include?(buname)
\r
525 rep = "#{buname} already queued for destruction"
\r
527 @destroy_q << buname
\r
528 rep = "#{buname} queued for destruction"
\r
530 return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")
\r
533 return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)
\r
534 return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password
\r
535 buser_array.delete_if { |u|
\r
536 u[:username] == buname
\r
538 @destroy_q.delete(buname)
\r
539 @bot.auth.load_array(buser_array, true)
\r
540 @bot.auth.set_changed
\r
542 return m.reply("failed: #{e}")
\r
544 return m.reply("botuser #{buname} destroyed")
\r
549 def auth_copy_ren_user(m, params)
\r
550 source = Auth::BotUser.sanitize_username(params[:source])
\r
551 dest = Auth::BotUser.sanitize_username(params[:dest])
\r
552 return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4
\r
554 buser_array = @bot.auth.save_array
\r
555 buser_hash = buser_array.inject({}) { |h, u|
\r
556 h[u[:username]] = u
\r
560 return m.reply("no such botuser #{source}") unless buser_hash.keys.include?(source)
\r
561 return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)
\r
563 copying = m.message.split[1] == "copy"
\r
567 buser_hash[source].each { |k, val|
\r
571 h = buser_hash[source]
\r
573 h[:username] = dest
\r
574 buser_array << h if copying
\r
576 @bot.auth.load_array(buser_array, true)
\r
577 @bot.auth.set_changed
\r
579 return m.reply("failed: #{e}")
\r
581 return m.reply("botuser #{source} copied to #{dest}") if copying
\r
582 return m.reply("botuser #{source} renamed to #{dest}")
\r
586 def auth_export(m, params)
\r
588 exportfile = "#{@bot.botclass}/new-auth.users"
\r
590 what = params[:things]
\r
592 has_to = what[-2] == "to"
\r
594 exportfile = "#{@bot.botclass}/#{what[-1]}"
\r
600 m.reply "selecting data to export ..."
\r
602 buser_array = @bot.auth.save_array
\r
603 buser_hash = buser_array.inject({}) { |h, u|
\r
604 h[u[:username]] = u
\r
609 we_want = buser_hash
\r
611 we_want = buser_hash.delete_if { |key, val|
\r
612 not what.include?(key)
\r
616 m.reply "preparing data for export ..."
\r
619 we_want.each { |k, val|
\r
626 yaml_hash[k][kk] = []
\r
628 yaml_hash[k][kk] << {
\r
629 :fullform => nm.fullform,
\r
630 :casemap => nm.casemap.to_s
\r
634 yaml_hash[k][kk] = v
\r
639 m.reply "failed to prepare data: #{e}"
\r
640 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
644 m.reply "exporting to #{exportfile} ..."
\r
646 # m.reply yaml_hash.inspect
\r
647 File.open(exportfile, "w") do |file|
\r
648 file.puts YAML::dump(yaml_hash)
\r
651 m.reply "failed to export users: #{e}"
\r
652 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
658 def auth_import(m, params)
\r
660 importfile = "#{@bot.botclass}/new-auth.users"
\r
662 what = params[:things]
\r
664 has_from = what[-2] == "from"
\r
666 importfile = "#{@bot.botclass}/#{what[-1]}"
\r
672 m.reply "reading #{importfile} ..."
\r
674 yaml_hash = YAML::load_file(importfile)
\r
676 m.reply "failed to import from: #{e}"
\r
677 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
681 # m.reply yaml_hash.inspect
\r
683 m.reply "selecting data to import ..."
\r
686 we_want = yaml_hash
\r
688 we_want = yaml_hash.delete_if { |key, val|
\r
689 not what.include?(key)
\r
693 m.reply "parsing data from import ..."
\r
698 yaml_hash.each { |k, val|
\r
699 buser_hash[k] = { :username => k }
\r
703 buser_hash[k][kk] = []
\r
705 buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)
\r
708 buser_hash[k][kk] = v
\r
713 m.reply "failed to parse data: #{e}"
\r
714 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
718 # m.reply buser_hash.inspect
\r
720 org_buser_array = @bot.auth.save_array
\r
721 org_buser_hash = org_buser_array.inject({}) { |h, u|
\r
722 h[u[:username]] = u
\r
726 # TODO we may want to do a(n optional) key-by-key merge
\r
728 org_buser_hash.merge!(buser_hash)
\r
729 new_buser_array = org_buser_hash.values
\r
730 @bot.auth.load_array(new_buser_array, true)
\r
731 @bot.auth.set_changed
\r
738 auth = AuthModule.new
\r
740 auth.map "user export *things",
\r
741 :action => 'auth_export',
\r
742 :defaults => { :things => ['all'] },
\r
743 :auth_path => ':manage:fedex:'
\r
745 auth.map "user import *things",
\r
746 :action => 'auth_import',
\r
747 :auth_path => ':manage:fedex:'
\r
749 auth.map "user create :name :password",
\r
750 :action => 'auth_create_user',
\r
751 :defaults => {:password => nil},
\r
752 :auth_path => ':manage:'
\r
754 auth.map "user [cancel] destroy :name :password",
\r
755 :action => 'auth_destroy_user',
\r
756 :defaults => { :password => nil },
\r
757 :auth_path => ':manage::destroy:'
\r
759 auth.map "user copy :source [to] :dest",
\r
760 :action => 'auth_copy_ren_user',
\r
761 :auth_path => ':manage:'
\r
763 auth.map "user rename :source [to] :dest",
\r
764 :action => 'auth_copy_ren_user',
\r
765 :auth_path => ':manage:'
\r
767 auth.default_auth("user::manage", false)
\r
769 auth.map "user tell :user the password for :botuser",
\r
770 :action => 'auth_tell_password',
\r
773 auth.map "user list",
\r
774 :action => 'auth_list_users',
\r
777 auth.map "user *data",
\r
778 :action => 'auth_manage_user'
\r
780 auth.default_auth("user", true)
\r
781 auth.default_auth("edit::other", false)
\r
784 :action => 'auth_whoami',
\r
785 :auth_path => '!*!'
\r
787 auth.map "auth :password",
\r
788 :action => 'auth_auth',
\r
790 :auth_path => '!login!'
\r
792 auth.map "login :botuser :password",
\r
793 :action => 'auth_login',
\r
795 :defaults => { :password => nil },
\r
796 :auth_path => '!login!'
\r
798 auth.map "login :botuser",
\r
799 :action => 'auth_login',
\r
800 :auth_path => '!login!'
\r
803 :action => 'auth_autologin',
\r
804 :auth_path => '!login!'
\r
806 auth.map "permissions set *args",
\r
807 :action => 'auth_edit_perm',
\r
808 :auth_path => ':edit::set:'
\r
810 auth.map "permissions reset *args",
\r
811 :action => 'auth_edit_perm',
\r
812 :auth_path => ':edit::reset:'
\r
814 auth.map "permissions view [for :user]",
\r
815 :action => 'auth_view_perm',
\r
818 auth.default_auth('*', false)
\r