]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/auth.rb
b38dd49aad99c27e5854069b6cba51b2890bee73
[user/henk/code/ruby/rbot.git] / lib / rbot / core / auth.rb
1 #-- vim:sw=2:et\r
2 #++\r
3 \r
4 \r
5 class AuthModule < CoreBotModule\r
6 \r
7   def initialize\r
8     super\r
9     load_array(:default, true)\r
10     debug "initialized auth. Botusers: #{@bot.auth.save_array.inspect}"\r
11   end\r
12 \r
13   def save\r
14     save_array\r
15   end\r
16 \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
22     end\r
23   end\r
24 \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
28   end\r
29 \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
35   #\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
39   #\r
40   def parse_args(ar, setting)\r
41     cmds = []\r
42     locs = []\r
43     warns = []\r
44     doing_cmds = true\r
45     next_must_be_chan = false\r
46     want_more = false\r
47     last_idx = 0\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
52           doing_cmds = false\r
53           next_must_be_chan = true if x == "on"\r
54           next\r
55         end\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
58         else\r
59           warns << ArgumentError.new("+ or - expected in front of #{x}") if setting\r
60         end\r
61         cmds << x\r
62       else # parse locations\r
63         if x[-1].chr == ','\r
64           want_more = true\r
65         else\r
66           want_more = false\r
67         end\r
68         case next_must_be_chan\r
69         when false\r
70           locs << x.gsub(/^here$/,'_').gsub(/^private$/,'?')\r
71         else\r
72           warns << ArgumentError("#{x} doesn't look like a channel name") unless @bot.server.supports[:chantypes].include?(x[0])\r
73           locs << x\r
74         end\r
75         unless want_more\r
76           last_idx = i\r
77           break\r
78         end\r
79       end\r
80     }\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
84   end\r
85 \r
86   def auth_edit_perm(m, params)\r
87 \r
88     setting = m.message.split[1] == "set"\r
89     splits = params[:args]\r
90 \r
91     has_for = splits[-2] == "for"\r
92     return usage unless has_for\r
93 \r
94     begin\r
95       user = @bot.auth.get_botuser(splits[-1].sub(/^all$/,"everyone"))\r
96     rescue\r
97       return m.reply("couldn't find botuser #{splits[-1]}")\r
98     end\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
101 \r
102     cmds, locs, warns = parse_args(splits, setting)\r
103     errs = warns.select { |w| w.kind_of?(Exception) }\r
104 \r
105     unless errs.empty?\r
106       m.reply "couldn't satisfy your request: #{errs.join(',')}"\r
107       return\r
108     end\r
109 \r
110     if locs.empty?\r
111       locs << "*"\r
112     end\r
113     begin\r
114       locs.each { |loc|\r
115         ch = loc\r
116         if m.private?\r
117           ch = "?" if loc == "_"\r
118         else\r
119           ch = m.target.to_s if loc == "_"\r
120         end\r
121         cmds.each { |setval|\r
122           if setting\r
123             val = setval[0].chr == '+'\r
124             cmd = setval[1..-1]\r
125             user.set_permission(cmd, val, ch)\r
126           else\r
127             cmd = setval\r
128             user.reset_permission(cmd, ch)\r
129           end\r
130         }\r
131       }\r
132     rescue => e\r
133       m.reply "something went wrong while trying to set the permissions"\r
134       raise\r
135     end\r
136     @bot.auth.set_changed\r
137     debug "user #{user} permissions changed"\r
138     m.okay\r
139   end\r
140 \r
141   def auth_view_perm(m, params)\r
142     begin\r
143       user = @bot.auth.get_botuser(params[:user].sub(/^all$/,"everyone"))\r
144     rescue\r
145       return m.reply("couldn't find botuser #{params[:user]}")\r
146     end\r
147     perm = user.perm\r
148     str = []\r
149     perm.each { |k, val|\r
150       next if val.perm.empty?\r
151       case k\r
152       when :*\r
153         str << "on any channel: "\r
154       when :"?"\r
155         str << "in private: "\r
156       else\r
157         str << "on #{k}: "\r
158       end\r
159       sub = []\r
160       val.perm.each { |cmd, bool|\r
161         sub << (bool ? "+" : "-")\r
162         sub.last << cmd.to_s\r
163       }\r
164       str.last << sub.join(', ')\r
165     }\r
166     if str.empty?\r
167       m.reply "no permissions set for #{user.username}"\r
168     else\r
169       m.reply "permissions for #{user.username}:: #{str.join('; ')}"\r
170     end\r
171   end\r
172 \r
173   def get_botuser_for(user)\r
174     @bot.auth.irc_to_botuser(user)\r
175   end\r
176 \r
177   def get_botusername_for(user)\r
178     get_botuser_for(user).username\r
179   end\r
180 \r
181   def welcome(user)\r
182     "welcome, #{get_botusername_for(user)}"\r
183   end\r
184 \r
185   def auth_login(m, params)\r
186     begin\r
187       case @bot.auth.login(m.source, params[:botuser], params[:password])\r
188       when true\r
189         m.reply welcome(m.source)\r
190         @bot.auth.set_changed\r
191       else\r
192         m.reply "sorry, can't do"\r
193       end\r
194     rescue => e\r
195       m.reply "couldn't login: #{e}"\r
196       raise\r
197     end\r
198   end\r
199 \r
200   def auth_autologin(m, params)\r
201     u = do_autologin(m.source)\r
202     case u.username\r
203     when 'everyone'\r
204       m.reply "I couldn't find anything to let you login automatically"\r
205     else\r
206       m.reply welcome(m.source)\r
207     end\r
208   end\r
209 \r
210   def do_autologin(user)\r
211     @bot.auth.autologin(user)\r
212   end\r
213 \r
214   def auth_whoami(m, params)\r
215     rep = ""\r
216     # if m.public?\r
217     #   rep << m.source.nick << ", "\r
218     # end\r
219     rep << "you are "\r
220     rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")\r
221     m.reply rep\r
222   end\r
223 \r
224   def help(plugin, topic="")\r
225     case topic\r
226     when /^login/\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
228     when /^whoami/\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
232     when /^permission/\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
234     when /^user show/\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
238     when /^user set/\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
242     when /^user reset/\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
244     when /^user tell/\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
248     when /^user list/\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
252     when /^user/\r
253       return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"\r
254     else\r
255       return "#{name}: login, whoami, permission syntax, permissions, user"\r
256     end\r
257   end\r
258 \r
259   def need_args(cmd)\r
260     "sorry, I need more arguments to #{cmd}"\r
261   end\r
262 \r
263   def not_args(cmd, *stuff)\r
264     "I can only #{cmd} these: #{stuff.join(', ')}"\r
265   end\r
266 \r
267   def set_prop(botuser, prop, val)\r
268     k = prop.to_s.gsub("-","_")\r
269     botuser.send( (k + "=").to_sym, val)\r
270   end\r
271 \r
272   def reset_prop(botuser, prop)\r
273     k = prop.to_s.gsub("-","_")\r
274     botuser.send( ("reset_"+k).to_sym)\r
275   end\r
276 \r
277   def ask_bool_prop(botuser, prop)\r
278     k = prop.to_s.gsub("-","_")\r
279     botuser.send( (k + "?").to_sym)\r
280   end\r
281 \r
282   def auth_manage_user(m, params)\r
283     splits = params[:data]\r
284 \r
285     cmd = splits.first\r
286     return auth_whoami(m, params) if cmd.nil?\r
287 \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
290     butarget = botuser\r
291 \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
296 \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
302 \r
303     case cmd.to_sym\r
304 \r
305     when :show\r
306       return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")\r
307 \r
308       case splits[1]\r
309       when nil, "all"\r
310         props = can_reset\r
311       when "password"\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
315         end\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
318       else\r
319         props = splits[1..-1]\r
320       end\r
321 \r
322       str = []\r
323 \r
324       props.each { |arg|\r
325         k = arg.to_sym\r
326         next if k == :password\r
327         case k\r
328         when *bools\r
329           str << "can"\r
330           str.last << "not" unless ask_bool_prop(butarget, k)\r
331           str.last << " #{k}"\r
332         when :netmasks\r
333           str << "knows "\r
334           if butarget.netmasks.empty?\r
335             str.last << "no netmasks"\r
336           else\r
337             str.last << butarget.netmasks.join(", ")\r
338           end\r
339         end\r
340       }\r
341       return m.reply("#{butarget.username} #{str.join('; ')}")\r
342 \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
346 \r
347       return m.reply(need_args(cmd)) unless splits[1]\r
348       things = []\r
349       skipped = []\r
350       splits[1..-1].each { |a|\r
351         arg = a.to_sym\r
352         if bools.include?(arg)\r
353           set_prop(butarget, arg, cmd.to_sym == :enable)\r
354           things << a\r
355         else\r
356           skipped << a\r
357         end\r
358       }\r
359 \r
360       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?\r
361       if things.empty?\r
362         m.reply "I haven't changed anything"\r
363       else\r
364         @bot.auth.set_changed\r
365         return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })\r
366       end\r
367 \r
368     when :set\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
371 \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
375       argarg = splits[2]\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
379       end\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
383 \r
384     when :reset\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
387 \r
388       return m.reply(need_args(cmd)) unless splits[1]\r
389       things = []\r
390       skipped = []\r
391       splits[1..-1].each { |a|\r
392         arg = a.to_sym\r
393         if can_reset.include?(arg)\r
394           reset_prop(butarget, arg)\r
395           things << a\r
396         else\r
397           skipped << a\r
398         end\r
399       }\r
400 \r
401       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?\r
402       if things.empty?\r
403         m.reply "I haven't changed anything"\r
404       else\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
408       end\r
409 \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
413 \r
414       arg = splits[1]\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
417       end\r
418 \r
419       method = cmd.to_sym == :add ? :add_netmask : :delete_netmask\r
420 \r
421       failed = []\r
422 \r
423       splits[2..-1].each { |mask|\r
424         begin\r
425           butarget.send(method, mask.to_irc_netmask(:server => @bot.server))\r
426         rescue\r
427           failed << mask\r
428         end\r
429       }\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
433 \r
434     else\r
435       m.reply "sorry, I don't know how to #{m.message}"\r
436     end\r
437   end\r
438 \r
439   def auth_tell_password(m, params)\r
440     user = params[:user]\r
441     begin\r
442       botuser = @bot.auth.get_botuser(params[:botuser])\r
443     rescue\r
444       return m.reply("coudln't find botuser #{params[:botuser]})")\r
445     end\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
448     @bot.say user, msg\r
449     @bot.say m.source, "I told #{user} that " + msg\r
450   end\r
451 \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
456     begin\r
457       bu = @bot.auth.create_botuser(name, password)\r
458       @bot.auth.set_changed\r
459     rescue => e\r
460       m.reply "failed to create #{name}: #{e}"\r
461       debug e.inspect + "\n" + e.backtrace.join("\n")\r
462       return\r
463     end\r
464     m.reply "created botuser #{bu.username}"\r
465   end\r
466 \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
471       list.map! { |x|\r
472         @destroy_q.include?(x) ? x + " (queued for destruction)" : x\r
473       }\r
474     end\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
477   end\r
478 \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
485 \r
486     buser_array = @bot.auth.save_array\r
487     buser_hash = buser_array.inject({}) { |h, u|\r
488       h[u[:username]] = u\r
489       h\r
490     }\r
491 \r
492     return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)\r
493 \r
494     if cancel\r
495       if @destroy_q.include?(buname)\r
496         @destroy_q.delete(buname)\r
497         m.reply "#{buname} removed from the destruction queue"\r
498       else\r
499         m.reply "#{buname} was not queued for destruction"\r
500       end\r
501       return\r
502     end\r
503 \r
504     if password.nil?\r
505       if @destroy_q.include?(buname)\r
506         rep = "#{buname} already queued for destruction"\r
507       else\r
508         @destroy_q << buname\r
509         rep = "#{buname} queued for destruction"\r
510       end\r
511       return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")\r
512     else\r
513       begin\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
518         }\r
519         @destroy_q.delete(buname)\r
520         @bot.auth.load_array(buser_array, true)\r
521         @bot.auth.set_changed\r
522       rescue => e\r
523         return m.reply("failed: #{e}")\r
524       end\r
525       return m.reply("botuser #{buname} destroyed")\r
526     end\r
527 \r
528   end\r
529 \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
534 \r
535     buser_array = @bot.auth.save_array\r
536     buser_hash = buser_array.inject({}) { |h, u|\r
537       h[u[:username]] = u\r
538       h\r
539     }\r
540 \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
543 \r
544     copying = m.message.split[1] == "copy"\r
545     begin\r
546       if copying\r
547         h = {}\r
548         buser_hash[source].each { |k, val|\r
549           h[k] = val.dup\r
550         }\r
551       else\r
552         h = buser_hash[source]\r
553       end\r
554       h[:username] = dest\r
555       buser_array << h if copying\r
556 \r
557       @bot.auth.load_array(buser_array, true)\r
558       @bot.auth.set_changed\r
559     rescue => e\r
560       return m.reply("failed: #{e}")\r
561     end\r
562     return m.reply("botuser #{source} copied to #{dest}") if copying\r
563     return m.reply("botuser #{source} renamed to #{dest}")\r
564 \r
565   end\r
566 \r
567   def auth_export(m, params)\r
568 \r
569     exportfile = "#{@bot.botclass}/new-auth.users"\r
570 \r
571     what = params[:things]\r
572 \r
573     has_to = what[-2] == "to"\r
574     if has_to\r
575       exportfile = "#{@bot.botclass}/#{what[-1]}"\r
576       what.slice!(-2,2)\r
577     end\r
578 \r
579     what.delete("all")\r
580 \r
581     m.reply "selecting data to export ..."\r
582 \r
583     buser_array = @bot.auth.save_array\r
584     buser_hash = buser_array.inject({}) { |h, u|\r
585       h[u[:username]] = u\r
586       h\r
587     }\r
588 \r
589     if what.empty?\r
590       we_want = buser_hash\r
591     else\r
592       we_want = buser_hash.delete_if { |key, val|\r
593         not what.include?(key)\r
594       }\r
595     end\r
596 \r
597     m.reply "preparing data for export ..."\r
598     begin\r
599       yaml_hash = {}\r
600       we_want.each { |k, val|\r
601         yaml_hash[k] = {}\r
602         val.each { |kk, v|\r
603           case kk\r
604           when :username\r
605             next\r
606           when :netmasks\r
607             yaml_hash[k][kk] = []\r
608             v.each { |nm|\r
609               yaml_hash[k][kk] << {\r
610                 :fullform => nm.fullform,\r
611                 :casemap => nm.casemap.to_s\r
612               }\r
613             }\r
614           else\r
615             yaml_hash[k][kk] = v\r
616           end\r
617         }\r
618       }\r
619     rescue => e\r
620       m.reply "failed to prepare data: #{e}"\r
621       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
622       return\r
623     end\r
624 \r
625     m.reply "exporting to #{exportfile} ..."\r
626     begin\r
627       # m.reply yaml_hash.inspect\r
628       File.open(exportfile, "w") do |file|\r
629         file.puts YAML::dump(yaml_hash)\r
630       end\r
631     rescue => e\r
632       m.reply "failed to export users: #{e}"\r
633       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
634       return\r
635     end\r
636     m.reply "done"\r
637   end\r
638 \r
639   def auth_import(m, params)\r
640 \r
641     importfile = "#{@bot.botclass}/new-auth.users"\r
642 \r
643     what = params[:things]\r
644 \r
645     has_from = what[-2] == "from"\r
646     if has_from\r
647       importfile = "#{@bot.botclass}/#{what[-1]}"\r
648       what.slice!(-2,2)\r
649     end\r
650 \r
651     what.delete("all")\r
652 \r
653     m.reply "reading #{importfile} ..."\r
654     begin\r
655       yaml_hash = YAML::load_file(importfile)\r
656     rescue => e\r
657       m.reply "failed to import from: #{e}"\r
658       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
659       return\r
660     end\r
661 \r
662     # m.reply yaml_hash.inspect\r
663 \r
664     m.reply "selecting data to import ..."\r
665 \r
666     if what.empty?\r
667       we_want = yaml_hash\r
668     else\r
669       we_want = yaml_hash.delete_if { |key, val|\r
670         not what.include?(key)\r
671       }\r
672     end\r
673 \r
674     m.reply "parsing data from import ..."\r
675 \r
676     buser_hash = {}\r
677 \r
678     begin\r
679       yaml_hash.each { |k, val|\r
680         buser_hash[k] = { :username => k }\r
681         val.each { |kk, v|\r
682           case kk\r
683           when :netmasks\r
684             buser_hash[k][kk] = []\r
685             v.each { |nm|\r
686               buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)\r
687             }\r
688           else\r
689             buser_hash[k][kk] = v\r
690           end\r
691         }\r
692       }\r
693     rescue => e\r
694       m.reply "failed to parse data: #{e}"\r
695       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
696       return\r
697     end\r
698 \r
699     # m.reply buser_hash.inspect\r
700 \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
704       h\r
705     }\r
706 \r
707     # TODO we may want to do a(n optional) key-by-key merge\r
708     #\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
713 \r
714     m.reply "done"\r
715   end\r
716 \r
717 end\r
718 \r
719 auth = AuthModule.new\r
720 \r
721 auth.map "user export *things",\r
722   :action => 'auth_export',\r
723   :defaults => { :things => ['all'] },\r
724   :auth_path => ':manage:fedex:'\r
725 \r
726 auth.map "user import *things",\r
727  :action => 'auth_import',\r
728  :auth_path => ':manage:fedex:'\r
729 \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
734 \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
739 \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
744 \r
745 auth.map "user copy :source :dest",\r
746   :action => 'auth_copy_ren_user',\r
747   :auth_path => ':manage:'\r
748 \r
749 auth.map "user rename :source :dest",\r
750   :action => 'auth_copy_ren_user',\r
751   :auth_path => ':manage:'\r
752 \r
753 auth.default_auth("user::manage", false)\r
754 \r
755 auth.map "user tell :user the password for :botuser",\r
756   :action => 'auth_tell_password',\r
757   :auth_path => '::'\r
758 \r
759 auth.map "user list",\r
760   :action => 'auth_list_users',\r
761   :auth_path => '::'\r
762 \r
763 auth.map "user *data",\r
764   :action => 'auth_manage_user'\r
765 \r
766 auth.default_auth("user", true)\r
767 auth.default_auth("edit::other", false)\r
768 \r
769 auth.map "whoami",\r
770   :action => 'auth_whoami',\r
771   :auth_path => '!*!'\r
772 \r
773 auth.map "login :botuser :password",\r
774   :action => 'auth_login',\r
775   :public => false,\r
776   :defaults => { :password => nil },\r
777   :auth_path => '!login!'\r
778 \r
779 auth.map "login :botuser",\r
780   :action => 'auth_login',\r
781   :auth_path => '!login!'\r
782 \r
783 auth.map "login",\r
784   :action => 'auth_autologin',\r
785   :auth_path => '!login!'\r
786 \r
787 auth.map "permissions set *args",\r
788   :action => 'auth_edit_perm',\r
789   :auth_path => ':edit::set:'\r
790 \r
791 auth.map "permissions reset *args",\r
792   :action => 'auth_edit_perm',\r
793   :auth_path => ':edit::reset:'\r
794 \r
795 auth.map "permissions view for :user",\r
796   :action => 'auth_view_perm',\r
797   :auth_path => '::'\r
798 \r
799 auth.default_auth('*', false)\r
800 \r