]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/auth.rb
Fix help. For real
[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(cmd, topic="")\r
225     case cmd\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/\r
231       case topic\r
232       when "syntax"\r
233         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
234       else\r
235         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
236       end\r
237     when "user"\r
238       case topic\r
239       when "show"\r
240         return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"\r
241       when /^(en|dis)able/\r
242         return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"\r
243       when "set"\r
244         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
245       when "add", "rm"\r
246         return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"\r
247       when "reset"\r
248         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
249       when "tell"\r
250         return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"\r
251       when "create"\r
252         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
253       when "list"\r
254         return "user list : lists all the botusers"\r
255       when "destroy"\r
256         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
257       else\r
258         return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"\r
259       end\r
260     else\r
261       return "#{name}: login, whoami, permission syntax, permissions, user"\r
262     end\r
263   end\r
264 \r
265   def need_args(cmd)\r
266     "sorry, I need more arguments to #{cmd}"\r
267   end\r
268 \r
269   def not_args(cmd, *stuff)\r
270     "I can only #{cmd} these: #{stuff.join(', ')}"\r
271   end\r
272 \r
273   def set_prop(botuser, prop, val)\r
274     k = prop.to_s.gsub("-","_")\r
275     botuser.send( (k + "=").to_sym, val)\r
276   end\r
277 \r
278   def reset_prop(botuser, prop)\r
279     k = prop.to_s.gsub("-","_")\r
280     botuser.send( ("reset_"+k).to_sym)\r
281   end\r
282 \r
283   def ask_bool_prop(botuser, prop)\r
284     k = prop.to_s.gsub("-","_")\r
285     botuser.send( (k + "?").to_sym)\r
286   end\r
287 \r
288   def auth_manage_user(m, params)\r
289     splits = params[:data]\r
290 \r
291     cmd = splits.first\r
292     return auth_whoami(m, params) if cmd.nil?\r
293 \r
294     botuser = get_botuser_for(m.source)\r
295     # By default, we do stuff on the botuser the irc user is bound to\r
296     butarget = botuser\r
297 \r
298     has_for = splits[-2] == "for"\r
299     butarget = @bot.auth.get_botuser(splits[-1]) if has_for\r
300     return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget\r
301     splits.slice!(-2,2) if has_for\r
302 \r
303     bools = [:autologin, :"login-by-mask"]\r
304     can_set = [:password]\r
305     can_addrm = [:netmasks]\r
306     can_reset = bools + can_set + can_addrm\r
307     can_show = can_reset + ["perms"]\r
308 \r
309     case cmd.to_sym\r
310 \r
311     when :show\r
312       return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")\r
313 \r
314       case splits[1]\r
315       when nil, "all"\r
316         props = can_reset\r
317       when "password"\r
318         if botuser != butarget\r
319           return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner\r
320           return m.reply("you can't ask for someone else's password")\r
321         end\r
322         return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?\r
323         return m.reply("the password for #{butarget.username} is #{butarget.password}")\r
324       else\r
325         props = splits[1..-1]\r
326       end\r
327 \r
328       str = []\r
329 \r
330       props.each { |arg|\r
331         k = arg.to_sym\r
332         next if k == :password\r
333         case k\r
334         when *bools\r
335           str << "can"\r
336           str.last << "not" unless ask_bool_prop(butarget, k)\r
337           str.last << " #{k}"\r
338         when :netmasks\r
339           str << "knows "\r
340           if butarget.netmasks.empty?\r
341             str.last << "no netmasks"\r
342           else\r
343             str.last << butarget.netmasks.join(", ")\r
344           end\r
345         end\r
346       }\r
347       return m.reply("#{butarget.username} #{str.join('; ')}")\r
348 \r
349     when :enable, :disable\r
350       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")\r
351       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
352 \r
353       return m.reply(need_args(cmd)) unless splits[1]\r
354       things = []\r
355       skipped = []\r
356       splits[1..-1].each { |a|\r
357         arg = a.to_sym\r
358         if bools.include?(arg)\r
359           set_prop(butarget, arg, cmd.to_sym == :enable)\r
360           things << a\r
361         else\r
362           skipped << a\r
363         end\r
364       }\r
365 \r
366       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?\r
367       if things.empty?\r
368         m.reply "I haven't changed anything"\r
369       else\r
370         @bot.auth.set_changed\r
371         return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })\r
372       end\r
373 \r
374     when :set\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
377 \r
378       return m.reply(need_args(cmd)) unless splits[1]\r
379       arg = splits[1].to_sym\r
380       return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)\r
381       argarg = splits[2]\r
382       return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg\r
383       if arg == :password && m.public?\r
384         return m.reply("is that a joke? setting the password in public?")\r
385       end\r
386       set_prop(butarget, arg, argarg)\r
387       @bot.auth.set_changed\r
388       auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })\r
389 \r
390     when :reset\r
391       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
392       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
393 \r
394       return m.reply(need_args(cmd)) unless splits[1]\r
395       things = []\r
396       skipped = []\r
397       splits[1..-1].each { |a|\r
398         arg = a.to_sym\r
399         if can_reset.include?(arg)\r
400           reset_prop(butarget, arg)\r
401           things << a\r
402         else\r
403           skipped << a\r
404         end\r
405       }\r
406 \r
407       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?\r
408       if things.empty?\r
409         m.reply "I haven't changed anything"\r
410       else\r
411         @bot.auth.set_changed\r
412         @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")\r
413         return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})\r
414       end\r
415 \r
416     when :add, :rm, :remove, :del, :delete\r
417       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
418       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
419 \r
420       arg = splits[1]\r
421       if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?\r
422         return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")\r
423       end\r
424 \r
425       method = cmd.to_sym == :add ? :add_netmask : :delete_netmask\r
426 \r
427       failed = []\r
428 \r
429       splits[2..-1].each { |mask|\r
430         begin\r
431           butarget.send(method, mask.to_irc_netmask(:server => @bot.server))\r
432         rescue\r
433           failed << mask\r
434         end\r
435       }\r
436       m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?\r
437       @bot.auth.set_changed\r
438       return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })\r
439 \r
440     else\r
441       m.reply "sorry, I don't know how to #{m.message}"\r
442     end\r
443   end\r
444 \r
445   def auth_tell_password(m, params)\r
446     user = params[:user]\r
447     begin\r
448       botuser = @bot.auth.get_botuser(params[:botuser])\r
449     rescue\r
450       return m.reply("coudln't find botuser #{params[:botuser]})")\r
451     end\r
452     m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner\r
453     msg = "the password for botuser #{botuser.username} is #{botuser.password}"\r
454     @bot.say user, msg\r
455     @bot.say m.source, "I told #{user} that " + msg\r
456   end\r
457 \r
458   def auth_create_user(m, params)\r
459     name = params[:name]\r
460     password = params[:password]\r
461     return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?\r
462     begin\r
463       bu = @bot.auth.create_botuser(name, password)\r
464       @bot.auth.set_changed\r
465     rescue => e\r
466       m.reply "failed to create #{name}: #{e}"\r
467       debug e.inspect + "\n" + e.backtrace.join("\n")\r
468       return\r
469     end\r
470     m.reply "created botuser #{bu.username}"\r
471   end\r
472 \r
473   def auth_list_users(m, params)\r
474     # TODO name regexp to filter results\r
475     list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']\r
476     if defined?(@destroy_q)\r
477       list.map! { |x|\r
478         @destroy_q.include?(x) ? x + " (queued for destruction)" : x\r
479       }\r
480     end\r
481     return m.reply("I have no botusers other than the default ones") if list.empty?\r
482     return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")\r
483   end\r
484 \r
485   def auth_destroy_user(m, params)\r
486     @destroy_q = [] unless defined?(@destroy_q)\r
487     buname = params[:name]\r
488     return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)\r
489     cancel = m.message.split[1] == 'cancel'\r
490     password = params[:password]\r
491 \r
492     buser_array = @bot.auth.save_array\r
493     buser_hash = buser_array.inject({}) { |h, u|\r
494       h[u[:username]] = u\r
495       h\r
496     }\r
497 \r
498     return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)\r
499 \r
500     if cancel\r
501       if @destroy_q.include?(buname)\r
502         @destroy_q.delete(buname)\r
503         m.reply "#{buname} removed from the destruction queue"\r
504       else\r
505         m.reply "#{buname} was not queued for destruction"\r
506       end\r
507       return\r
508     end\r
509 \r
510     if password.nil?\r
511       if @destroy_q.include?(buname)\r
512         rep = "#{buname} already queued for destruction"\r
513       else\r
514         @destroy_q << buname\r
515         rep = "#{buname} queued for destruction"\r
516       end\r
517       return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")\r
518     else\r
519       begin\r
520         return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)\r
521         return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password\r
522         buser_array.delete_if { |u|\r
523           u[:username] == buname\r
524         }\r
525         @destroy_q.delete(buname)\r
526         @bot.auth.load_array(buser_array, true)\r
527         @bot.auth.set_changed\r
528       rescue => e\r
529         return m.reply("failed: #{e}")\r
530       end\r
531       return m.reply("botuser #{buname} destroyed")\r
532     end\r
533 \r
534   end\r
535 \r
536   def auth_copy_ren_user(m, params)\r
537     source = Auth::BotUser.sanitize_username(params[:source])\r
538     dest = Auth::BotUser.sanitize_username(params[:dest])\r
539     return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4\r
540 \r
541     buser_array = @bot.auth.save_array\r
542     buser_hash = buser_array.inject({}) { |h, u|\r
543       h[u[:username]] = u\r
544       h\r
545     }\r
546 \r
547     return m.reply("no such botuser #{source}") unless buser_hash.keys.include?(source)\r
548     return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)\r
549 \r
550     copying = m.message.split[1] == "copy"\r
551     begin\r
552       if copying\r
553         h = {}\r
554         buser_hash[source].each { |k, val|\r
555           h[k] = val.dup\r
556         }\r
557       else\r
558         h = buser_hash[source]\r
559       end\r
560       h[:username] = dest\r
561       buser_array << h if copying\r
562 \r
563       @bot.auth.load_array(buser_array, true)\r
564       @bot.auth.set_changed\r
565     rescue => e\r
566       return m.reply("failed: #{e}")\r
567     end\r
568     return m.reply("botuser #{source} copied to #{dest}") if copying\r
569     return m.reply("botuser #{source} renamed to #{dest}")\r
570 \r
571   end\r
572 \r
573   def auth_export(m, params)\r
574 \r
575     exportfile = "#{@bot.botclass}/new-auth.users"\r
576 \r
577     what = params[:things]\r
578 \r
579     has_to = what[-2] == "to"\r
580     if has_to\r
581       exportfile = "#{@bot.botclass}/#{what[-1]}"\r
582       what.slice!(-2,2)\r
583     end\r
584 \r
585     what.delete("all")\r
586 \r
587     m.reply "selecting data to export ..."\r
588 \r
589     buser_array = @bot.auth.save_array\r
590     buser_hash = buser_array.inject({}) { |h, u|\r
591       h[u[:username]] = u\r
592       h\r
593     }\r
594 \r
595     if what.empty?\r
596       we_want = buser_hash\r
597     else\r
598       we_want = buser_hash.delete_if { |key, val|\r
599         not what.include?(key)\r
600       }\r
601     end\r
602 \r
603     m.reply "preparing data for export ..."\r
604     begin\r
605       yaml_hash = {}\r
606       we_want.each { |k, val|\r
607         yaml_hash[k] = {}\r
608         val.each { |kk, v|\r
609           case kk\r
610           when :username\r
611             next\r
612           when :netmasks\r
613             yaml_hash[k][kk] = []\r
614             v.each { |nm|\r
615               yaml_hash[k][kk] << {\r
616                 :fullform => nm.fullform,\r
617                 :casemap => nm.casemap.to_s\r
618               }\r
619             }\r
620           else\r
621             yaml_hash[k][kk] = v\r
622           end\r
623         }\r
624       }\r
625     rescue => e\r
626       m.reply "failed to prepare data: #{e}"\r
627       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
628       return\r
629     end\r
630 \r
631     m.reply "exporting to #{exportfile} ..."\r
632     begin\r
633       # m.reply yaml_hash.inspect\r
634       File.open(exportfile, "w") do |file|\r
635         file.puts YAML::dump(yaml_hash)\r
636       end\r
637     rescue => e\r
638       m.reply "failed to export users: #{e}"\r
639       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
640       return\r
641     end\r
642     m.reply "done"\r
643   end\r
644 \r
645   def auth_import(m, params)\r
646 \r
647     importfile = "#{@bot.botclass}/new-auth.users"\r
648 \r
649     what = params[:things]\r
650 \r
651     has_from = what[-2] == "from"\r
652     if has_from\r
653       importfile = "#{@bot.botclass}/#{what[-1]}"\r
654       what.slice!(-2,2)\r
655     end\r
656 \r
657     what.delete("all")\r
658 \r
659     m.reply "reading #{importfile} ..."\r
660     begin\r
661       yaml_hash = YAML::load_file(importfile)\r
662     rescue => e\r
663       m.reply "failed to import from: #{e}"\r
664       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
665       return\r
666     end\r
667 \r
668     # m.reply yaml_hash.inspect\r
669 \r
670     m.reply "selecting data to import ..."\r
671 \r
672     if what.empty?\r
673       we_want = yaml_hash\r
674     else\r
675       we_want = yaml_hash.delete_if { |key, val|\r
676         not what.include?(key)\r
677       }\r
678     end\r
679 \r
680     m.reply "parsing data from import ..."\r
681 \r
682     buser_hash = {}\r
683 \r
684     begin\r
685       yaml_hash.each { |k, val|\r
686         buser_hash[k] = { :username => k }\r
687         val.each { |kk, v|\r
688           case kk\r
689           when :netmasks\r
690             buser_hash[k][kk] = []\r
691             v.each { |nm|\r
692               buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)\r
693             }\r
694           else\r
695             buser_hash[k][kk] = v\r
696           end\r
697         }\r
698       }\r
699     rescue => e\r
700       m.reply "failed to parse data: #{e}"\r
701       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
702       return\r
703     end\r
704 \r
705     # m.reply buser_hash.inspect\r
706 \r
707     org_buser_array = @bot.auth.save_array\r
708     org_buser_hash = org_buser_array.inject({}) { |h, u|\r
709       h[u[:username]] = u\r
710       h\r
711     }\r
712 \r
713     # TODO we may want to do a(n optional) key-by-key merge\r
714     #\r
715     org_buser_hash.merge!(buser_hash)\r
716     new_buser_array = org_buser_hash.values\r
717     @bot.auth.load_array(new_buser_array, true)\r
718     @bot.auth.set_changed\r
719 \r
720     m.reply "done"\r
721   end\r
722 \r
723 end\r
724 \r
725 auth = AuthModule.new\r
726 \r
727 auth.map "user export *things",\r
728   :action => 'auth_export',\r
729   :defaults => { :things => ['all'] },\r
730   :auth_path => ':manage:fedex:'\r
731 \r
732 auth.map "user import *things",\r
733  :action => 'auth_import',\r
734  :auth_path => ':manage:fedex:'\r
735 \r
736 auth.map "user create :name :password",\r
737   :action => 'auth_create_user',\r
738   :defaults => {:password => nil},\r
739   :auth_path => ':manage:'\r
740 \r
741 auth.map "user cancel destroy :name :password",\r
742   :action => 'auth_destroy_user',\r
743   :defaults => { :password => nil },\r
744   :auth_path => ':manage::destroy:'\r
745 \r
746 auth.map "user destroy :name :password",\r
747   :action => 'auth_destroy_user',\r
748   :defaults => { :password => nil },\r
749   :auth_path => ':manage:'\r
750 \r
751 auth.map "user copy :source :dest",\r
752   :action => 'auth_copy_ren_user',\r
753   :auth_path => ':manage:'\r
754 \r
755 auth.map "user rename :source :dest",\r
756   :action => 'auth_copy_ren_user',\r
757   :auth_path => ':manage:'\r
758 \r
759 auth.default_auth("user::manage", false)\r
760 \r
761 auth.map "user tell :user the password for :botuser",\r
762   :action => 'auth_tell_password',\r
763   :auth_path => '::'\r
764 \r
765 auth.map "user list",\r
766   :action => 'auth_list_users',\r
767   :auth_path => '::'\r
768 \r
769 auth.map "user *data",\r
770   :action => 'auth_manage_user'\r
771 \r
772 auth.default_auth("user", true)\r
773 auth.default_auth("edit::other", false)\r
774 \r
775 auth.map "whoami",\r
776   :action => 'auth_whoami',\r
777   :auth_path => '!*!'\r
778 \r
779 auth.map "login :botuser :password",\r
780   :action => 'auth_login',\r
781   :public => false,\r
782   :defaults => { :password => nil },\r
783   :auth_path => '!login!'\r
784 \r
785 auth.map "login :botuser",\r
786   :action => 'auth_login',\r
787   :auth_path => '!login!'\r
788 \r
789 auth.map "login",\r
790   :action => 'auth_autologin',\r
791   :auth_path => '!login!'\r
792 \r
793 auth.map "permissions set *args",\r
794   :action => 'auth_edit_perm',\r
795   :auth_path => ':edit::set:'\r
796 \r
797 auth.map "permissions reset *args",\r
798   :action => 'auth_edit_perm',\r
799   :auth_path => ':edit::reset:'\r
800 \r
801 auth.map "permissions view for :user",\r
802   :action => 'auth_view_perm',\r
803   :auth_path => '::'\r
804 \r
805 auth.default_auth('*', false)\r
806 \r