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 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 user = @bot.auth.get_botuser(params[:user].sub(/^all$/,"everyone"))
\r
145 return m.reply("couldn't find botuser #{params[:user]}")
\r
149 perm.each { |k, val|
\r
150 next if val.perm.empty?
\r
153 str << "on any channel: "
\r
155 str << "in private: "
\r
160 val.perm.each { |cmd, bool|
\r
161 sub << (bool ? "+" : "-")
\r
162 sub.last << cmd.to_s
\r
164 str.last << sub.join(', ')
\r
167 m.reply "no permissions set for #{user.username}"
\r
169 m.reply "permissions for #{user.username}:: #{str.join('; ')}"
\r
173 def get_botuser_for(user)
\r
174 @bot.auth.irc_to_botuser(user)
\r
177 def get_botusername_for(user)
\r
178 get_botuser_for(user).username
\r
182 "welcome, #{get_botusername_for(user)}"
\r
185 def auth_login(m, params)
\r
187 case @bot.auth.login(m.source, params[:botuser], params[:password])
\r
189 m.reply welcome(m.source)
\r
190 @bot.auth.set_changed
\r
192 m.reply "sorry, can't do"
\r
195 m.reply "couldn't login: #{e}"
\r
200 def auth_autologin(m, params)
\r
201 u = do_autologin(m.source)
\r
204 m.reply "I couldn't find anything to let you login automatically"
\r
206 m.reply welcome(m.source)
\r
210 def do_autologin(user)
\r
211 @bot.auth.autologin(user)
\r
214 def auth_whoami(m, params)
\r
217 # rep << m.source.nick << ", "
\r
220 rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")
\r
224 def help(plugin, topic="")
\r
227 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
229 return "whoami: names the botuser you're linked to"
\r
230 when /^permission syntax/
\r
231 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
233 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
235 return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"
\r
236 when /^user (en|dis)able/
\r
237 return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"
\r
239 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
240 when /^user (add|rm)/
\r
241 return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"
\r
243 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
245 return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"
\r
246 when /^user create/
\r
247 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
249 return "user list : lists all the botusers"
\r
250 when /^user destroy/
\r
251 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
253 return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"
\r
255 return "#{name}: login, whoami, permission syntax, permissions, user"
\r
260 "sorry, I need more arguments to #{cmd}"
\r
263 def not_args(cmd, *stuff)
\r
264 "I can only #{cmd} these: #{stuff.join(', ')}"
\r
267 def set_prop(botuser, prop, val)
\r
268 k = prop.to_s.gsub("-","_")
\r
269 botuser.send( (k + "=").to_sym, val)
\r
272 def reset_prop(botuser, prop)
\r
273 k = prop.to_s.gsub("-","_")
\r
274 botuser.send( ("reset_"+k).to_sym)
\r
277 def ask_bool_prop(botuser, prop)
\r
278 k = prop.to_s.gsub("-","_")
\r
279 botuser.send( (k + "?").to_sym)
\r
282 def auth_manage_user(m, params)
\r
283 splits = params[:data]
\r
286 return auth_whoami(m, params) if cmd.nil?
\r
288 botuser = get_botuser_for(m.source)
\r
289 # By default, we do stuff on the botuser the irc user is bound to
\r
292 has_for = splits[-2] == "for"
\r
293 butarget = @bot.auth.get_botuser(splits[-1]) if has_for
\r
294 return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget
\r
295 splits.slice!(-2,2) if has_for
\r
297 bools = [:autologin, :"login-by-mask"]
\r
298 can_set = [:password]
\r
299 can_addrm = [:netmasks]
\r
300 can_reset = bools + can_set + can_addrm
\r
301 can_show = can_reset + ["perms"]
\r
306 return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")
\r
312 if botuser != butarget
\r
313 return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner
\r
314 return m.reply("you can't ask for someone else's password")
\r
316 return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?
\r
317 return m.reply("the password for #{butarget.username} is #{butarget.password}")
\r
319 props = splits[1..-1]
\r
326 next if k == :password
\r
330 str.last << "not" unless ask_bool_prop(butarget, k)
\r
331 str.last << " #{k}"
\r
334 if butarget.netmasks.empty?
\r
335 str.last << "no netmasks"
\r
337 str.last << butarget.netmasks.join(", ")
\r
341 return m.reply("#{butarget.username} #{str.join('; ')}")
\r
343 when :enable, :disable
\r
344 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")
\r
345 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
347 return m.reply(need_args(cmd)) unless splits[1]
\r
350 splits[1..-1].each { |a|
\r
352 if bools.include?(arg)
\r
353 set_prop(butarget, arg, cmd.to_sym == :enable)
\r
360 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?
\r
362 m.reply "I haven't changed anything"
\r
364 @bot.auth.set_changed
\r
365 return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })
\r
369 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
370 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
372 return m.reply(need_args(cmd)) unless splits[1]
\r
373 arg = splits[1].to_sym
\r
374 return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)
\r
376 return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg
\r
377 if arg == :password && m.public?
\r
378 return m.reply("is that a joke? setting the password in public?")
\r
380 set_prop(butarget, arg, argarg)
\r
381 @bot.auth.set_changed
\r
382 auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })
\r
385 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
386 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
388 return m.reply(need_args(cmd)) unless splits[1]
\r
391 splits[1..-1].each { |a|
\r
393 if can_reset.include?(arg)
\r
394 reset_prop(butarget, arg)
\r
401 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?
\r
403 m.reply "I haven't changed anything"
\r
405 @bot.auth.set_changed
\r
406 @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")
\r
407 return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})
\r
410 when :add, :rm, :remove, :del, :delete
\r
411 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
412 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
415 if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?
\r
416 return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")
\r
419 method = cmd.to_sym == :add ? :add_netmask : :delete_netmask
\r
423 splits[2..-1].each { |mask|
\r
425 butarget.send(method, mask.to_irc_netmask(:server => @bot.server))
\r
430 m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?
\r
431 @bot.auth.set_changed
\r
432 return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })
\r
435 m.reply "sorry, I don't know how to #{m.message}"
\r
439 def auth_tell_password(m, params)
\r
440 user = params[:user]
\r
442 botuser = @bot.auth.get_botuser(params[:botuser])
\r
444 return m.reply("coudln't find botuser #{params[:botuser]})")
\r
446 m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner
\r
447 msg = "the password for botuser #{botuser.username} is #{botuser.password}"
\r
449 @bot.say m.source, "I told #{user} that " + msg
\r
452 def auth_create_user(m, params)
\r
453 name = params[:name]
\r
454 password = params[:password]
\r
455 return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?
\r
457 bu = @bot.auth.create_botuser(name, password)
\r
458 @bot.auth.set_changed
\r
460 m.reply "failed to create #{name}: #{e}"
\r
461 debug e.inspect + "\n" + e.backtrace.join("\n")
\r
464 m.reply "created botuser #{bu.username}"
\r
467 def auth_list_users(m, params)
\r
468 # TODO name regexp to filter results
\r
469 list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']
\r
470 if defined?(@destroy_q)
\r
472 @destroy_q.include?(x) ? x + " (queued for destruction)" : x
\r
475 return m.reply("I have no botusers other than the default ones") if list.empty?
\r
476 return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")
\r
479 def auth_destroy_user(m, params)
\r
480 @destroy_q = [] unless defined?(@destroy_q)
\r
481 buname = params[:name]
\r
482 return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)
\r
483 cancel = m.message.split[1] == 'cancel'
\r
484 password = params[:password]
\r
486 buser_array = @bot.auth.save_array
\r
487 buser_hash = buser_array.inject({}) { |h, u|
\r
488 h[u[:username]] = u
\r
492 return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)
\r
495 if @destroy_q.include?(buname)
\r
496 @destroy_q.delete(buname)
\r
497 m.reply "#{buname} removed from the destruction queue"
\r
499 m.reply "#{buname} was not queued for destruction"
\r
505 if @destroy_q.include?(buname)
\r
506 rep = "#{buname} already queued for destruction"
\r
508 @destroy_q << buname
\r
509 rep = "#{buname} queued for destruction"
\r
511 return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")
\r
514 return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)
\r
515 return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password
\r
516 buser_array.delete_if { |u|
\r
517 u[:username] == buname
\r
519 @destroy_q.delete(buname)
\r
520 @bot.auth.load_array(buser_array, true)
\r
521 @bot.auth.set_changed
\r
523 return m.reply("failed: #{e}")
\r
525 return m.reply("botuser #{buname} destroyed")
\r
530 def auth_copy_ren_user(m, params)
\r
531 source = Auth::BotUser.sanitize_username(params[:source])
\r
532 dest = Auth::BotUser.sanitize_username(params[:dest])
\r
533 return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4
\r
535 buser_array = @bot.auth.save_array
\r
536 buser_hash = buser_array.inject({}) { |h, u|
\r
537 h[u[:username]] = u
\r
541 return m.reply("no such botuser #{source}") unless buser_hash.keys.include?(source)
\r
542 return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)
\r
544 copying = m.message.split[1] == "copy"
\r
548 buser_hash[source].each { |k, val|
\r
552 h = buser_hash[source]
\r
554 h[:username] = dest
\r
555 buser_array << h if copying
\r
557 @bot.auth.load_array(buser_array, true)
\r
558 @bot.auth.set_changed
\r
560 return m.reply("failed: #{e}")
\r
562 return m.reply("botuser #{source} copied to #{dest}") if copying
\r
563 return m.reply("botuser #{source} renamed to #{dest}")
\r
567 def auth_export(m, params)
\r
569 exportfile = "#{@bot.botclass}/new-auth.users"
\r
571 what = params[:things]
\r
573 has_to = what[-2] == "to"
\r
575 exportfile = "#{@bot.botclass}/#{what[-1]}"
\r
581 m.reply "selecting data to export ..."
\r
583 buser_array = @bot.auth.save_array
\r
584 buser_hash = buser_array.inject({}) { |h, u|
\r
585 h[u[:username]] = u
\r
590 we_want = buser_hash
\r
592 we_want = buser_hash.delete_if { |key, val|
\r
593 not what.include?(key)
\r
597 m.reply "preparing data for export ..."
\r
600 we_want.each { |k, val|
\r
607 yaml_hash[k][kk] = []
\r
609 yaml_hash[k][kk] << {
\r
610 :fullform => nm.fullform,
\r
611 :casemap => nm.casemap.to_s
\r
615 yaml_hash[k][kk] = v
\r
620 m.reply "failed to prepare data: #{e}"
\r
621 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
625 m.reply "exporting to #{exportfile} ..."
\r
627 # m.reply yaml_hash.inspect
\r
628 File.open(exportfile, "w") do |file|
\r
629 file.puts YAML::dump(yaml_hash)
\r
632 m.reply "failed to export users: #{e}"
\r
633 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
639 def auth_import(m, params)
\r
641 importfile = "#{@bot.botclass}/new-auth.users"
\r
643 what = params[:things]
\r
645 has_from = what[-2] == "from"
\r
647 importfile = "#{@bot.botclass}/#{what[-1]}"
\r
653 m.reply "reading #{importfile} ..."
\r
655 yaml_hash = YAML::load_file(importfile)
\r
657 m.reply "failed to import from: #{e}"
\r
658 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
662 # m.reply yaml_hash.inspect
\r
664 m.reply "selecting data to import ..."
\r
667 we_want = yaml_hash
\r
669 we_want = yaml_hash.delete_if { |key, val|
\r
670 not what.include?(key)
\r
674 m.reply "parsing data from import ..."
\r
679 yaml_hash.each { |k, val|
\r
680 buser_hash[k] = { :username => k }
\r
684 buser_hash[k][kk] = []
\r
686 buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)
\r
689 buser_hash[k][kk] = v
\r
694 m.reply "failed to parse data: #{e}"
\r
695 debug e.backtrace.dup.unshift(e.inspect).join("\n")
\r
699 # m.reply buser_hash.inspect
\r
701 org_buser_array = @bot.auth.save_array
\r
702 org_buser_hash = org_buser_array.inject({}) { |h, u|
\r
703 h[u[:username]] = u
\r
707 # TODO we may want to do a(n optional) key-by-key merge
\r
709 org_buser_hash.merge!(buser_hash)
\r
710 new_buser_array = org_buser_hash.values
\r
711 @bot.auth.load_array(new_buser_array, true)
\r
712 @bot.auth.set_changed
\r
719 auth = AuthModule.new
\r
721 auth.map "user export *things",
\r
722 :action => 'auth_export',
\r
723 :defaults => { :things => ['all'] },
\r
724 :auth_path => ':manage:fedex:'
\r
726 auth.map "user import *things",
\r
727 :action => 'auth_import',
\r
728 :auth_path => ':manage:fedex:'
\r
730 auth.map "user create :name :password",
\r
731 :action => 'auth_create_user',
\r
732 :defaults => {:password => nil},
\r
733 :auth_path => ':manage:'
\r
735 auth.map "user cancel destroy :name :password",
\r
736 :action => 'auth_destroy_user',
\r
737 :defaults => { :password => nil },
\r
738 :auth_path => ':manage::destroy:'
\r
740 auth.map "user destroy :name :password",
\r
741 :action => 'auth_destroy_user',
\r
742 :defaults => { :password => nil },
\r
743 :auth_path => ':manage:'
\r
745 auth.map "user copy :source :dest",
\r
746 :action => 'auth_copy_ren_user',
\r
747 :auth_path => ':manage:'
\r
749 auth.map "user rename :source :dest",
\r
750 :action => 'auth_copy_ren_user',
\r
751 :auth_path => ':manage:'
\r
753 auth.default_auth("user::manage", false)
\r
755 auth.map "user tell :user the password for :botuser",
\r
756 :action => 'auth_tell_password',
\r
759 auth.map "user list",
\r
760 :action => 'auth_list_users',
\r
763 auth.map "user *data",
\r
764 :action => 'auth_manage_user'
\r
766 auth.default_auth("user", true)
\r
767 auth.default_auth("edit::other", false)
\r
770 :action => 'auth_whoami',
\r
771 :auth_path => '!*!'
\r
773 auth.map "login :botuser :password",
\r
774 :action => 'auth_login',
\r
776 :defaults => { :password => nil },
\r
777 :auth_path => '!login!'
\r
779 auth.map "login :botuser",
\r
780 :action => 'auth_login',
\r
781 :auth_path => '!login!'
\r
784 :action => 'auth_autologin',
\r
785 :auth_path => '!login!'
\r
787 auth.map "permissions set *args",
\r
788 :action => 'auth_edit_perm',
\r
789 :auth_path => ':edit::set:'
\r
791 auth.map "permissions reset *args",
\r
792 :action => 'auth_edit_perm',
\r
793 :auth_path => ':edit::reset:'
\r
795 auth.map "permissions view for :user",
\r
796 :action => 'auth_view_perm',
\r
799 auth.default_auth('*', false)
\r