4 # * user destroy: should work in two phases:
\r
5 # * <code>user destroy _botuser_</code> would queue _botuser_ for
\r
7 # * <code>user destroy _botuser_ _password_</code> would actually destroy
\r
8 # _botuser_ if it was queued and the _password_ is correct
\r
12 # It should be fairly easy to implement all of this stuff by using
\r
13 # @bot.auth.load_array and @bot.auth.save_array: this means it can be tested
\r
14 # live and without any need to touch the rbot kernel file +botuser.rb+
\r
18 class AuthModule < CoreBotModule
\r
22 load_array(:default, true)
\r
23 debug "Initialized auth. Botusers: #{@bot.auth.save_array.inspect}"
\r
30 def save_array(key=:default)
\r
31 if @bot.auth.changed?
\r
32 @registry[key] = @bot.auth.save_array
\r
33 @bot.auth.reset_changed
\r
34 debug "saved botusers (#{key}): #{@registry[key].inspect}"
\r
38 def load_array(key=:default, forced=false)
\r
39 debug "loading botusers (#{key}): #{@registry[key].inspect}"
\r
40 @bot.auth.load_array(@registry[key], forced) if @registry.has_key?(key)
\r
43 # The permission parameters accept arguments with the following syntax:
\r
44 # cmd_path... [on #chan .... | in here | in private]
\r
45 # This auxiliary method scans the array _ar_ to see if it matches
\r
46 # the given syntax: it expects + or - signs in front of _cmd_path_
\r
47 # elements when _setting_ = true
\r
49 # It returns an array whose first element is the array of cmd_path,
\r
50 # the second element is an array of locations and third an array of
\r
51 # warnings occurred while parsing the strings
\r
53 def parse_args(ar, setting)
\r
58 next_must_be_chan = false
\r
61 ar.each_with_index { |x, i|
\r
62 if doing_cmds # parse cmd_path
\r
63 # check if the list is done
\r
64 if x == "on" or x == "in"
\r
66 next_must_be_chan = true if x == "on"
\r
69 if "+-".include?(x[0])
\r
70 warns << ArgumentError("please do not use + or - in front of command #{x} when resetting") unless setting
\r
72 warns << ArgumentError("+ or - expected in front of #{x}") if setting
\r
75 else # parse locations
\r
81 case next_must_be_chan
\r
83 locs << x.gsub(/^here$/,'_').gsub(/^private$/,'?')
\r
85 warns << ArgumentError("#{x} doesn't look like a channel name") unless @bot.server.supports[:chantypes].include?(x[0])
\r
94 warns << "trailing comma" if wants_more
\r
95 warns << "you probably forgot a comma" unless last_idx == ar.length - 1
\r
96 return cmds, locs, warns
\r
99 def auth_set(m, params)
\r
100 cmds, locs, warns = parse_args(params[:args])
\r
101 errs = warns.select { |w| w.kind_of?(Exception) }
\r
103 m.reply "couldn't satisfy your request: #{errs.join(',')}"
\r
106 user = params[:user].sub(/^all$/,"everyone")
\r
108 bu = @bot.auth.get_botuser(user)
\r
110 return m.reply "couldn't find botuser #{user}"
\r
119 ch = "?" if loc == "_"
\r
121 ch = m.target.to_s if loc == "_"
\r
123 cmds.each { |setval|
\r
124 val = setval[0].chr == '+'
\r
125 cmd = setval[1..-1]
\r
126 bu.set_permission(cmd, val, ch)
\r
130 m.reply "Something went wrong while trying to set the permissions"
\r
133 @bot.auth.set_changed
\r
134 debug "User #{user} permissions changed"
\r
135 m.reply "Ok, #{user} now also has permissions #{params[:args].join(' ')}"
\r
138 def get_botuser_for(user)
\r
139 @bot.auth.irc_to_botuser(user)
\r
142 def get_botusername_for(user)
\r
143 get_botuser_for(user).username
\r
147 "welcome, #{get_botusername_for(user)}"
\r
150 def auth_login(m, params)
\r
152 case @bot.auth.login(m.source, params[:botuser], params[:password])
\r
154 m.reply welcome(m.source)
\r
155 @bot.auth.set_changed
\r
157 m.reply "sorry, can't do"
\r
160 m.reply "couldn't login: #{e}"
\r
165 def auth_autologin(m, params)
\r
166 u = do_autologin(m.source)
\r
169 m.reply "I couldn't find anything to let you login automatically"
\r
171 m.reply welcome(m.source)
\r
175 def do_autologin(user)
\r
176 @bot.auth.autologin(user)
\r
179 def auth_whoami(m, params)
\r
182 # rep << m.source.nick << ", "
\r
185 rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")
\r
189 def help(plugin, topic="")
\r
192 return "login [<botuser>] [<pass>]: logs in to the bot as botuser <botuser> with password <pass>. <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
194 return "whoami: names the botuser you're linked to"
\r
195 when /^permission syntax/
\r
196 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
198 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
200 return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"
\r
201 when /^user (en|dis)able/
\r
202 return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"
\r
204 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
205 when /^user (add|rm)/
\r
206 return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"
\r
208 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
210 return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"
\r
211 when /^user create/
\r
212 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
214 return "user list : lists all the botusers"
\r
215 when /^user destroy/
\r
216 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
218 return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"
\r
220 return "#{name}: login, whoami, permission syntax, permissions, user"
\r
225 "sorry, I need more arguments to #{cmd}"
\r
228 def not_args(cmd, *stuff)
\r
229 "I can only #{cmd} these: #{stuff.join(', ')}"
\r
232 def set_prop(botuser, prop, val)
\r
233 k = prop.to_s.gsub("-","_")
\r
234 botuser.send( (k + "=").to_sym, val)
\r
237 def reset_prop(botuser, prop)
\r
238 k = prop.to_s.gsub("-","_")
\r
239 botuser.send( ("reset_"+k).to_sym)
\r
242 def ask_bool_prop(botuser, prop)
\r
243 k = prop.to_s.gsub("-","_")
\r
244 botuser.send( (k + "?").to_sym)
\r
247 def auth_manage_user(m, params)
\r
248 splits = params[:data]
\r
251 return auth_whoami(m, params) if cmd.nil?
\r
253 botuser = get_botuser_for(m.source)
\r
254 # By default, we do stuff on the botuser the irc user is bound to
\r
257 has_for = splits[-2] == "for"
\r
258 butarget = @bot.auth.get_botuser(splits[-1]) if has_for
\r
259 return m.reply "you can't mess with #{butarget.username}" if butarget == @bot.auth.botowner && botuser != butarget
\r
260 splits.slice!(-2,2) if has_for
\r
262 bools = [:autologin, :"login-by-mask"]
\r
263 can_set = [:password]
\r
264 can_addrm = [:netmasks]
\r
265 can_reset = bools + can_set + can_addrm
\r
270 return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")
\r
276 if botuser != butarget
\r
277 return m.reply "no way I'm telling you the master password!" if butarget == @bot.auth.botowner
\r
278 return m.reply "you can't ask for someone else's password"
\r
280 return m.reply "c'mon, you can't be asking me seriously to tell you the password in public!" if m.public?
\r
281 return m.reply "the password for #{butarget.username} is #{butarget.password}"
\r
283 props = splits[1..-1]
\r
290 next if k == :password
\r
294 str.last << "not" unless ask_bool_prop(butarget, k)
\r
295 str.last << " #{k}"
\r
298 if butarget.netmasks.empty?
\r
299 str.last << "no netmasks"
\r
301 str.last << butarget.netmasks.join(", ")
\r
305 return m.reply "#{butarget.username} #{str.join('; ')}"
\r
307 when :enable, :disable
\r
308 return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")
\r
309 return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
311 return m.reply need_args(cmd) unless splits[1]
\r
314 splits[1..-1].each { |a|
\r
316 if bools.include?(arg)
\r
317 set_prop(butarget, arg, cmd.to_sym == :enable)
\r
324 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?
\r
326 m.reply "I haven't changed anything"
\r
328 @bot.auth.set_changed
\r
329 return auth_manage_user(m, {:data => ["show"] + things })
\r
333 return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
334 return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
336 return m.reply need_args(cmd) unless splits[1]
\r
337 arg = splits[1].to_sym
\r
338 return m.reply not_args(cmd, *can_set) unless can_set.include?(arg)
\r
340 return m.reply need_args([cmd, splits[1]].join(" ")) unless argarg
\r
341 if arg == :password && m.public?
\r
342 return m.reply "is that a joke? setting the password in public?"
\r
344 set_prop(butarget, arg, argarg)
\r
345 @bot.auth.set_changed
\r
346 auth_manage_user(m, {:data => ["show", arg] })
\r
349 return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
350 return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
352 return m.reply need_args(cmd) unless splits[1]
\r
355 splits[1..-1].each { |a|
\r
357 if can_reset.include?(arg)
\r
358 reset_prop(butarget, arg)
\r
365 m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?
\r
367 m.reply "I haven't changed anything"
\r
369 @bot.auth.set_changed
\r
370 @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")
\r
371 return auth_manage_user(m, {:data => ["show"] + things - ["password"]})
\r
374 when :add, :rm, :remove, :del, :delete
\r
375 return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")
\r
376 return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")
\r
379 if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?
\r
380 return m.reply "I can only add/remove netmasks. See +help user add+ for more instructions"
\r
383 method = cmd.to_sym == :add ? :add_netmask : :delete_netmask
\r
387 splits[2..-1].each { |mask|
\r
389 butarget.send(method, mask.to_irc_netmask(:server => @bot.server))
\r
394 m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?
\r
395 @bot.auth.set_changed
\r
396 return auth_manage_user(m, {:data => ["show", "netmasks"] })
\r
399 m.reply "sorry, I don't know how to #{m.message}"
\r
403 def auth_tell_password(m, params)
\r
404 user = params[:user]
\r
406 botuser = @bot.auth.get_botuser(params[:botuser])
\r
408 return m.reply "coudln't find botuser #{params[:botuser]})"
\r
410 m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner
\r
411 msg = "the password for botuser #{botuser.username} is #{botuser.password}"
\r
413 @bot.say m.source, "I told #{user} that " + msg
\r
416 def auth_create_user(m, params)
\r
417 name = params[:name]
\r
418 password = params[:password]
\r
419 return m.reply "are you nuts, creating a botuser with a publicly known password?" if m.public? and not password.nil?
\r
421 bu = @bot.auth.create_botuser(name, password)
\r
422 @bot.auth.set_changed
\r
424 return m.reply "Failed to create #{name}: #{e}"
\r
425 debug e.inspect + "\n" + e.backtrace.join("\n")
\r
427 m.reply "Created botuser #{bu.username}"
\r
430 def auth_list_users(m, params)
\r
431 # TODO name regexp to filter results
\r
432 list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']
\r
433 if defined?(@destroy_q)
\r
435 @destroy_q.include?(x) ? x + " (queued for destruction)" : x
\r
438 return m.reply "I have no botusers other than the default ones" if list.empty?
\r
439 return m.reply "Botuser#{'s' if list.length > 1}: #{list.join(', ')}"
\r
442 def auth_destroy_user(m, params)
\r
443 @destroy_q = [] unless defined?(@destroy_q)
\r
444 buname = params[:name]
\r
445 returm m.reply "You can't destroy #{buname}" if ["everyone", "owner"].include?(buname)
\r
446 cancel = m.message.split[1] == 'cancel'
\r
447 password = params[:password]
\r
448 buser_array = @bot.auth.save_array
\r
449 buser_hash = buser_array.inject({}) { |h, u|
\r
450 h[u[:username]] = u
\r
454 return m.reply "No such botuser #{buname}" unless buser_hash.keys.include?(buname)
\r
457 if @destroy_q.include?(buname)
\r
458 @destroy_q.delete(buname)
\r
459 m.reply "#{buname} removed from the destruction queue"
\r
461 m.reply "#{buname} was not queued for destruction"
\r
467 if @destroy_q.include?(buname)
\r
468 rep = "#{buname} already queued for destruction"
\r
470 @destroy_q << buname
\r
471 rep = "#{buname} queued for destruction"
\r
473 return m.reply rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it"
\r
476 return m.reply "#{buname} is not queued for destruction yet" unless @destroy_q.include?(buname)
\r
477 return m.reply "wrong password for #{buname}" unless buser_hash[buname][:password] == password
\r
478 buser_array.delete_if { |u|
\r
479 u[:username] == buname
\r
481 @destroy_q.delete(buname)
\r
482 @bot.auth.load_array(buser_array, true)
\r
484 return m.reply "failed: #{e}"
\r
486 return m.reply "user #{buname} destroyed"
\r
493 auth = AuthModule.new
\r
495 auth.map "user create :name :password",
\r
496 :action => 'auth_create_user',
\r
497 :defaults => {:password => nil},
\r
498 :auth_path => 'user::manage::create!'
\r
500 auth.map "user cancel destroy :name :password",
\r
501 :action => 'auth_destroy_user',
\r
502 :defaults => { :password => nil },
\r
503 :auth_path => 'user::manage::destroy::cancel!'
\r
505 auth.map "user destroy :name :password",
\r
506 :action => 'auth_destroy_user',
\r
507 :defaults => { :password => nil },
\r
508 :auth_path => 'user::manage::destroy!'
\r
510 auth.default_auth("user::manage", false)
\r
512 auth.map "user tell :user the password for :botuser",
\r
513 :action => 'auth_tell_password',
\r
514 :auth_path => 'user::tell'
\r
516 auth.map "user list",
\r
517 :action => 'auth_list_users',
\r
518 :auth_path => 'user::list!'
\r
520 auth.map "user *data",
\r
521 :action => 'auth_manage_user'
\r
523 auth.default_auth("user", true)
\r
524 auth.default_auth("edit::other", false)
\r
527 :action => 'auth_whoami',
\r
528 :auth_path => '!*!'
\r
530 auth.map "login :botuser :password",
\r
531 :action => 'auth_login',
\r
533 :defaults => { :password => nil },
\r
534 :auth_path => '!login!'
\r
536 auth.map "login :botuser",
\r
537 :action => 'auth_login',
\r
538 :auth_path => '!login!'
\r
541 :action => 'auth_autologin',
\r
542 :auth_path => '!login!'
\r
544 auth.map "permissions set *args for :user",
\r
545 :action => 'auth_set',
\r
546 :auth_path => ':edit::set:'
\r
548 auth.map "permissions reset *args for :user",
\r
549 :action => 'auth_reset',
\r
550 :auth_path => ':edit::reset:'
\r
552 auth.default_auth('*', false)
\r