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("please do not use + or - in front of command #{x} when resetting") unless setting
\r
59 warns << ArgumentError("+ 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 wants_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_set(m, params)
\r
87 cmds, locs, warns = parse_args(params[:args])
\r
88 errs = warns.select { |w| w.kind_of?(Exception) }
\r
90 m.reply "couldn't satisfy your request: #{errs.join(',')}"
\r
93 user = params[:user].sub(/^all$/,"everyone")
\r
95 bu = @bot.auth.get_botuser(user)
\r
97 return m.reply("couldn't find botuser #{user}")
\r
106 ch = "?" if loc == "_"
\r
108 ch = m.target.to_s if loc == "_"
\r
110 cmds.each { |setval|
\r
111 val = setval[0].chr == '+'
\r
112 cmd = setval[1..-1]
\r
113 bu.set_permission(cmd, val, ch)
\r
117 m.reply "something went wrong while trying to set the permissions"
\r
120 @bot.auth.set_changed
\r
121 debug "user #{user} permissions changed"
\r
122 m.reply "ok, #{user} now also has permissions #{params[:args].join(' ')}"
\r
125 def get_botuser_for(user)
\r
126 @bot.auth.irc_to_botuser(user)
\r
129 def get_botusername_for(user)
\r
130 get_botuser_for(user).username
\r
134 "welcome, #{get_botusername_for(user)}"
\r
137 def auth_login(m, params)
\r
139 case @bot.auth.login(m.source, params[:botuser], params[:password])
\r
141 m.reply welcome(m.source)
\r
142 @bot.auth.set_changed
\r
144 m.reply "sorry, can't do"
\r
147 m.reply "couldn't login: #{e}"
\r
152 def auth_autologin(m, params)
\r
153 u = do_autologin(m.source)
\r
156 m.reply "I couldn't find anything to let you login automatically"
\r
158 m.reply welcome(m.source)
\r
162 def do_autologin(user)
\r
163 @bot.auth.autologin(user)
\r
166 def auth_whoami(m, params)
\r
169 # rep << m.source.nick << ", "
\r
172 rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")
\r
176 def help(plugin, topic="")
\r
179 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
181 return "whoami: names the botuser you're linked to"
\r
182 when /^permission syntax/
\r
183 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
185 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
187 return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"
\r
188 when /^user (en|dis)able/
\r
189 return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"
\r
191 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
192 when /^user (add|rm)/
\r
193 return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"
\r
195 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
197 return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"
\r
198 when /^user create/
\r
199 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
201 return "user list : lists all the botusers"
\r
202 when /^user destroy/
\r
203 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
205 return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"
\r
207 return "#{name}: login, whoami, permission syntax, permissions, user"
\r
212 "sorry, I need more arguments to #{cmd}"
\r
215 def not_args(cmd, *stuff)
\r
216 "I can only #{cmd} these: #{stuff.join(', ')}"
\r
219 def set_prop(botuser, prop, val)
\r
220 k = prop.to_s.gsub("-","_")
\r
221 botuser.send( (k + "=").to_sym, val)
\r
224 def reset_prop(botuser, prop)
\r
225 k = prop.to_s.gsub("-","_")
\r
226 botuser.send( ("reset_"+k).to_sym)
\r
229 def ask_bool_prop(botuser, prop)
\r
230 k = prop.to_s.gsub("-","_")
\r
231 botuser.send( (k + "?").to_sym)
\r
234 def auth_manage_user(m, params)
\r
235 splits = params[:data]
\r
238 return auth_whoami(m, params) if cmd.nil?
\r
240 botuser = get_botuser_for(m.source)
\r
241 # By default, we do stuff on the botuser the irc user is bound to
\r
244 has_for = splits[-2] == "for"
\r
245 butarget = @bot.auth.get_botuser(splits[-1]) if has_for
\r
246 return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget
\r
247 splits.slice!(-2,2) if has_for
\r
249 bools = [:autologin, :"login-by-mask"]
\r
250 can_set = [:password]
\r
251 can_addrm = [:netmasks]
\r
252 can_reset = bools + can_set + can_addrm
\r
257 return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")
\r
263 if botuser != butarget
\r
264 return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner
\r
265 return m.reply("you can't ask for someone else's password")
\r
267 return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?
\r
268 return m.reply("the password for #{butarget.username} is #{butarget.password}")
\r
270 props = splits[1..-1]
\r
277 next if k == :password
\r
281 str.last << "not" unless ask_bool_prop(butarget, k)
\r
282 str.last << " #{k}"
\r
285 if butarget.netmasks.empty?
\r
286 str.last << "no netmasks"
\r
288 str.last << butarget.netmasks.join(", ")
\r
292 return m.reply("#{butarget.username} #{str.join('; ')}")
\r
294 when :enable, :disable
\r
295 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")
\r
296 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
298 return m.reply(need_args(cmd)) unless splits[1]
\r
301 splits[1..-1].each { |a|
\r
303 if bools.include?(arg)
\r
304 set_prop(butarget, arg, cmd.to_sym == :enable)
\r
311 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?
\r
313 m.reply "I haven't changed anything"
\r
315 @bot.auth.set_changed
\r
316 return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })
\r
320 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
321 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
323 return m.reply(need_args(cmd)) unless splits[1]
\r
324 arg = splits[1].to_sym
\r
325 return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)
\r
327 return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg
\r
328 if arg == :password && m.public?
\r
329 return m.reply("is that a joke? setting the password in public?")
\r
331 set_prop(butarget, arg, argarg)
\r
332 @bot.auth.set_changed
\r
333 auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })
\r
336 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
337 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
339 return m.reply(need_args(cmd)) unless splits[1]
\r
342 splits[1..-1].each { |a|
\r
344 if can_reset.include?(arg)
\r
345 reset_prop(butarget, arg)
\r
352 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?
\r
354 m.reply "I haven't changed anything"
\r
356 @bot.auth.set_changed
\r
357 @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")
\r
358 return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})
\r
361 when :add, :rm, :remove, :del, :delete
\r
362 return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
363 return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
366 if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?
\r
367 return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")
\r
370 method = cmd.to_sym == :add ? :add_netmask : :delete_netmask
\r
374 splits[2..-1].each { |mask|
\r
376 butarget.send(method, mask.to_irc_netmask(:server => @bot.server))
\r
381 m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?
\r
382 @bot.auth.set_changed
\r
383 return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })
\r
386 m.reply "sorry, I don't know how to #{m.message}"
\r
390 def auth_tell_password(m, params)
\r
391 user = params[:user]
\r
393 botuser = @bot.auth.get_botuser(params[:botuser])
\r
395 return m.reply("coudln't find botuser #{params[:botuser]})")
\r
397 m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner
\r
398 msg = "the password for botuser #{botuser.username} is #{botuser.password}"
\r
400 @bot.say m.source, "I told #{user} that " + msg
\r
403 def auth_create_user(m, params)
\r
404 name = params[:name]
\r
405 password = params[:password]
\r
406 return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?
\r
408 bu = @bot.auth.create_botuser(name, password)
\r
409 @bot.auth.set_changed
\r
411 m.reply "failed to create #{name}: #{e}"
\r
412 debug e.inspect + "\n" + e.backtrace.join("\n")
\r
415 m.reply "created botuser #{bu.username}"
\r
418 def auth_list_users(m, params)
\r
419 # TODO name regexp to filter results
\r
420 list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']
\r
421 if defined?(@destroy_q)
\r
423 @destroy_q.include?(x) ? x + " (queued for destruction)" : x
\r
426 return m.reply("I have no botusers other than the default ones") if list.empty?
\r
427 return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")
\r
430 def auth_destroy_user(m, params)
\r
431 @destroy_q = [] unless defined?(@destroy_q)
\r
432 buname = params[:name]
\r
433 return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)
\r
434 cancel = m.message.split[1] == 'cancel'
\r
435 password = params[:password]
\r
437 buser_array = @bot.auth.save_array
\r
438 buser_hash = buser_array.inject({}) { |h, u|
\r
439 h[u[:username]] = u
\r
443 return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)
\r
446 if @destroy_q.include?(buname)
\r
447 @destroy_q.delete(buname)
\r
448 m.reply "#{buname} removed from the destruction queue"
\r
450 m.reply "#{buname} was not queued for destruction"
\r
456 if @destroy_q.include?(buname)
\r
457 rep = "#{buname} already queued for destruction"
\r
459 @destroy_q << buname
\r
460 rep = "#{buname} queued for destruction"
\r
462 return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")
\r
465 return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)
\r
466 return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password
\r
467 buser_array.delete_if { |u|
\r
468 u[:username] == buname
\r
470 @destroy_q.delete(buname)
\r
471 @bot.auth.load_array(buser_array, true)
\r
472 @bot.auth.set_changed
\r
474 return m.reply("failed: #{e}")
\r
476 return m.reply("botuser #{buname} destroyed")
\r
481 def auth_copy_ren_user(m, params)
\r
482 source = Auth::BotUser.sanitize_username(params[:source])
\r
483 dest = Auth::BotUser.sanitize_username(params[:dest])
\r
484 return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4
\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 #{source}") unless buser_hash.keys.include?(source)
\r
493 return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)
\r
495 copying = m.message.split[1] == "copy"
\r
498 h = buser_hash[source].dup
\r
500 h = buser_hash[source]
\r
502 h[:username] = dest
\r
503 buser_array << h if copying
\r
505 @bot.auth.load_array(buser_array, true)
\r
506 @bot.auth.set_changed
\r
508 return m.reply("failed: #{e}")
\r
510 return m.reply("botuser #{source} copied to #{dest}") if copying
\r
511 return m.reply("botuser #{source} renamed to #{dest}")
\r
517 auth = AuthModule.new
\r
519 auth.map "user create :name :password",
\r
520 :action => 'auth_create_user',
\r
521 :defaults => {:password => nil},
\r
522 :auth_path => 'user::manage::create!'
\r
524 auth.map "user cancel destroy :name :password",
\r
525 :action => 'auth_destroy_user',
\r
526 :defaults => { :password => nil },
\r
527 :auth_path => 'user::manage::destroy::cancel!'
\r
529 auth.map "user destroy :name :password",
\r
530 :action => 'auth_destroy_user',
\r
531 :defaults => { :password => nil },
\r
532 :auth_path => 'user::manage::destroy!'
\r
534 auth.map "user copy :source :dest",
\r
535 :action => 'auth_copy_ren_user',
\r
536 :auth_path => 'user::manage::copy!'
\r
538 auth.map "user rename :source :dest",
\r
539 :action => 'auth_copy_ren_user',
\r
540 :auth_path => 'user::manage::rename!'
\r
542 auth.default_auth("user::manage", false)
\r
544 auth.map "user tell :user the password for :botuser",
\r
545 :action => 'auth_tell_password',
\r
546 :auth_path => 'user::tell'
\r
548 auth.map "user list",
\r
549 :action => 'auth_list_users',
\r
550 :auth_path => 'user::list!'
\r
552 auth.map "user *data",
\r
553 :action => 'auth_manage_user'
\r
555 auth.default_auth("user", true)
\r
556 auth.default_auth("edit::other", false)
\r
559 :action => 'auth_whoami',
\r
560 :auth_path => '!*!'
\r
562 auth.map "login :botuser :password",
\r
563 :action => 'auth_login',
\r
565 :defaults => { :password => nil },
\r
566 :auth_path => '!login!'
\r
568 auth.map "login :botuser",
\r
569 :action => 'auth_login',
\r
570 :auth_path => '!login!'
\r
573 :action => 'auth_autologin',
\r
574 :auth_path => '!login!'
\r
576 auth.map "permissions set *args for :user",
\r
577 :action => 'auth_set',
\r
578 :auth_path => ':edit::set:'
\r
580 auth.map "permissions reset *args for :user",
\r
581 :action => 'auth_reset',
\r
582 :auth_path => ':edit::reset:'
\r
584 auth.default_auth('*', false)
\r