]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/auth.rb
Plugin header boilerplating.
[user/henk/code/ruby/rbot.git] / lib / rbot / core / auth.rb
1 #-- vim:sw=2:et\r
2 #++\r
3 #\r
4 # :title: rbot auth management from IRC\r
5 #\r
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>\r
7 # Copyright:: (C) 2006,2007 Giuseppe Bilotta\r
8 # License:: GPL v2\r
9 \r
10 class AuthModule < CoreBotModule\r
11 \r
12   def initialize\r
13     super\r
14     load_array(:default, true)\r
15     debug "initialized auth. Botusers: #{@bot.auth.save_array.inspect}"\r
16   end\r
17 \r
18   def save\r
19     save_array\r
20   end\r
21 \r
22   def save_array(key=:default)\r
23     if @bot.auth.changed?\r
24       @registry[key] = @bot.auth.save_array\r
25       @bot.auth.reset_changed\r
26       debug "saved botusers (#{key}): #{@registry[key].inspect}"\r
27     end\r
28   end\r
29 \r
30   def load_array(key=:default, forced=false)\r
31     debug "loading botusers (#{key}): #{@registry[key].inspect}"\r
32     @bot.auth.load_array(@registry[key], forced) if @registry.has_key?(key)\r
33   end\r
34 \r
35   # The permission parameters accept arguments with the following syntax:\r
36   #   cmd_path... [on #chan .... | in here | in private]\r
37   # This auxiliary method scans the array _ar_ to see if it matches\r
38   # the given syntax: it expects + or - signs in front of _cmd_path_\r
39   # elements when _setting_ = true\r
40   #\r
41   # It returns an array whose first element is the array of cmd_path,\r
42   # the second element is an array of locations and third an array of\r
43   # warnings occurred while parsing the strings\r
44   #\r
45   def parse_args(ar, setting)\r
46     cmds = []\r
47     locs = []\r
48     warns = []\r
49     doing_cmds = true\r
50     next_must_be_chan = false\r
51     want_more = false\r
52     last_idx = 0\r
53     ar.each_with_index { |x, i|\r
54       if doing_cmds # parse cmd_path\r
55         # check if the list is done\r
56         if x == "on" or x == "in"\r
57           doing_cmds = false\r
58           next_must_be_chan = true if x == "on"\r
59           next\r
60         end\r
61         if "+-".include?(x[0])\r
62           warns << ArgumentError.new("please do not use + or - in front of command #{x} when resetting") unless setting\r
63         else\r
64           warns << ArgumentError.new("+ or - expected in front of #{x}") if setting\r
65         end\r
66         cmds << x\r
67       else # parse locations\r
68         if x[-1].chr == ','\r
69           want_more = true\r
70         else\r
71           want_more = false\r
72         end\r
73         case next_must_be_chan\r
74         when false\r
75           locs << x.gsub(/^here$/,'_').gsub(/^private$/,'?')\r
76         else\r
77           warns << ArgumentError("#{x} doesn't look like a channel name") unless @bot.server.supports[:chantypes].include?(x[0])\r
78           locs << x\r
79         end\r
80         unless want_more\r
81           last_idx = i\r
82           break\r
83         end\r
84       end\r
85     }\r
86     warns << "trailing comma" if want_more\r
87     warns << "you probably forgot a comma" unless last_idx == ar.length - 1\r
88     return cmds, locs, warns\r
89   end\r
90 \r
91   def auth_edit_perm(m, params)\r
92 \r
93     setting = m.message.split[1] == "set"\r
94     splits = params[:args]\r
95 \r
96     has_for = splits[-2] == "for"\r
97     return usage(m) unless has_for\r
98 \r
99     begin\r
100       user = @bot.auth.get_botuser(splits[-1].sub(/^all$/,"everyone"))\r
101     rescue\r
102       return m.reply("couldn't find botuser #{splits[-1]}")\r
103     end\r
104     return m.reply("you can't change permissions for #{user.username}") if user == @bot.auth.botowner\r
105     splits.slice!(-2,2) if has_for\r
106 \r
107     cmds, locs, warns = parse_args(splits, setting)\r
108     errs = warns.select { |w| w.kind_of?(Exception) }\r
109 \r
110     unless errs.empty?\r
111       m.reply "couldn't satisfy your request: #{errs.join(',')}"\r
112       return\r
113     end\r
114 \r
115     if locs.empty?\r
116       locs << "*"\r
117     end\r
118     begin\r
119       locs.each { |loc|\r
120         ch = loc\r
121         if m.private?\r
122           ch = "?" if loc == "_"\r
123         else\r
124           ch = m.target.to_s if loc == "_"\r
125         end\r
126         cmds.each { |setval|\r
127           if setting\r
128             val = setval[0].chr == '+'\r
129             cmd = setval[1..-1]\r
130             user.set_permission(cmd, val, ch)\r
131           else\r
132             cmd = setval\r
133             user.reset_permission(cmd, ch)\r
134           end\r
135         }\r
136       }\r
137     rescue => e\r
138       m.reply "something went wrong while trying to set the permissions"\r
139       raise\r
140     end\r
141     @bot.auth.set_changed\r
142     debug "user #{user} permissions changed"\r
143     m.okay\r
144   end\r
145 \r
146   def auth_view_perm(m, params)\r
147     begin\r
148       if params[:user].nil?\r
149         user = get_botusername_for(m.source)\r
150         return m.reply("you are owner, you can do anything") if user == @bot.auth.botwoner\r
151       else\r
152         user = @bot.auth.get_botuser(params[:user].sub(/^all$/,"everyone"))\r
153         return m.reply("owner can do anything") if user.username == "owner" \r
154       end\r
155     rescue\r
156       return m.reply("couldn't find botuser #{params[:user]}")\r
157     end\r
158     perm = user.perm\r
159     str = []\r
160     perm.each { |k, val|\r
161       next if val.perm.empty?\r
162       case k\r
163       when :*\r
164         str << "on any channel: "\r
165       when :"?"\r
166         str << "in private: "\r
167       else\r
168         str << "on #{k}: "\r
169       end\r
170       sub = []\r
171       val.perm.each { |cmd, bool|\r
172         sub << (bool ? "+" : "-")\r
173         sub.last << cmd.to_s\r
174       }\r
175       str.last << sub.join(', ')\r
176     }\r
177     if str.empty?\r
178       m.reply "no permissions set for #{user.username}"\r
179     else\r
180       m.reply "permissions for #{user.username}:: #{str.join('; ')}"\r
181     end\r
182   end\r
183 \r
184   def get_botuser_for(user)\r
185     @bot.auth.irc_to_botuser(user)\r
186   end\r
187 \r
188   def get_botusername_for(user)\r
189     get_botuser_for(user).username\r
190   end\r
191 \r
192   def welcome(user)\r
193     "welcome, #{get_botusername_for(user)}"\r
194   end\r
195 \r
196   def auth_auth(m, params)\r
197     params[:botuser] = 'owner'\r
198     auth_login(m,params)\r
199   end\r
200 \r
201   def auth_login(m, params)\r
202     begin\r
203       case @bot.auth.login(m.source, params[:botuser], params[:password])\r
204       when true\r
205         m.reply welcome(m.source)\r
206         @bot.auth.set_changed\r
207       else\r
208         m.reply "sorry, can't do"\r
209       end\r
210     rescue => e\r
211       m.reply "couldn't login: #{e}"\r
212       raise\r
213     end\r
214   end\r
215 \r
216   def auth_autologin(m, params)\r
217     u = do_autologin(m.source)\r
218     case u.username\r
219     when 'everyone'\r
220       m.reply "I couldn't find anything to let you login automatically"\r
221     else\r
222       m.reply welcome(m.source)\r
223     end\r
224   end\r
225 \r
226   def do_autologin(user)\r
227     @bot.auth.autologin(user)\r
228   end\r
229 \r
230   def auth_whoami(m, params)\r
231     rep = ""\r
232     # if m.public?\r
233     #   rep << m.source.nick << ", "\r
234     # end\r
235     rep << "you are "\r
236     rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")\r
237     m.reply rep\r
238   end\r
239 \r
240   def help(cmd, topic="")\r
241     case cmd\r
242     when "login"\r
243       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
244     when "whoami"\r
245       return "whoami: names the botuser you're linked to"\r
246     when /^permission/\r
247       case topic\r
248       when "syntax"\r
249         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
250       when "set", "reset", "[re]set", "(re)set"\r
251         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
252       when "view"\r
253         return "permissions view [for <user>]: display the permissions for user <user>"\r
254       else\r
255         return "topics: syntax, (re)set, view"\r
256       end\r
257     when "user"\r
258       case topic\r
259       when "show"\r
260         return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"\r
261       when /^(en|dis)able/\r
262         return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"\r
263       when "set"\r
264         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
265       when "add", "rm"\r
266         return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"\r
267       when "reset"\r
268         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
269       when "tell"\r
270         return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"\r
271       when "create"\r
272         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
273       when "list"\r
274         return "user list : lists all the botusers"\r
275       when "destroy"\r
276         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
277       else\r
278         return "user show, enable|disable, add|rm netmask, set, reset, tell, create, list, destroy"\r
279       end\r
280     else\r
281       return "#{name}: login, whoami, permission syntax, permissions [re]set, permissions view, user"\r
282     end\r
283   end\r
284 \r
285   def need_args(cmd)\r
286     "sorry, I need more arguments to #{cmd}"\r
287   end\r
288 \r
289   def not_args(cmd, *stuff)\r
290     "I can only #{cmd} these: #{stuff.join(', ')}"\r
291   end\r
292 \r
293   def set_prop(botuser, prop, val)\r
294     k = prop.to_s.gsub("-","_")\r
295     botuser.send( (k + "=").to_sym, val)\r
296   end\r
297 \r
298   def reset_prop(botuser, prop)\r
299     k = prop.to_s.gsub("-","_")\r
300     botuser.send( ("reset_"+k).to_sym)\r
301   end\r
302 \r
303   def ask_bool_prop(botuser, prop)\r
304     k = prop.to_s.gsub("-","_")\r
305     botuser.send( (k + "?").to_sym)\r
306   end\r
307 \r
308   def auth_manage_user(m, params)\r
309     splits = params[:data]\r
310 \r
311     cmd = splits.first\r
312     return auth_whoami(m, params) if cmd.nil?\r
313 \r
314     botuser = get_botuser_for(m.source)\r
315     # By default, we do stuff on the botuser the irc user is bound to\r
316     butarget = botuser\r
317 \r
318     has_for = splits[-2] == "for"\r
319     butarget = @bot.auth.get_botuser(splits[-1]) if has_for\r
320     return m.reply("you can't mess with #{butarget.username}") if butarget == @bot.auth.botowner && botuser != butarget\r
321     splits.slice!(-2,2) if has_for\r
322 \r
323     bools = [:autologin, :"login-by-mask"]\r
324     can_set = [:password]\r
325     can_addrm = [:netmasks]\r
326     can_reset = bools + can_set + can_addrm\r
327     can_show = can_reset + ["perms"]\r
328 \r
329     case cmd.to_sym\r
330 \r
331     when :show\r
332       return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")\r
333 \r
334       case splits[1]\r
335       when nil, "all"\r
336         props = can_reset\r
337       when "password"\r
338         if botuser != butarget\r
339           return m.reply("no way I'm telling you the master password!") if butarget == @bot.auth.botowner\r
340           return m.reply("you can't ask for someone else's password")\r
341         end\r
342         return m.reply("c'mon, you can't be asking me seriously to tell you the password in public!") if m.public?\r
343         return m.reply("the password for #{butarget.username} is #{butarget.password}")\r
344       else\r
345         props = splits[1..-1]\r
346       end\r
347 \r
348       str = []\r
349 \r
350       props.each { |arg|\r
351         k = arg.to_sym\r
352         next if k == :password\r
353         case k\r
354         when *bools\r
355           str << "can"\r
356           str.last << "not" unless ask_bool_prop(butarget, k)\r
357           str.last << " #{k}"\r
358         when :netmasks\r
359           str << "knows "\r
360           if butarget.netmasks.empty?\r
361             str.last << "no netmasks"\r
362           else\r
363             str.last << butarget.netmasks.join(", ")\r
364           end\r
365         end\r
366       }\r
367       return m.reply("#{butarget.username} #{str.join('; ')}")\r
368 \r
369     when :enable, :disable\r
370       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")\r
371       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
372 \r
373       return m.reply(need_args(cmd)) unless splits[1]\r
374       things = []\r
375       skipped = []\r
376       splits[1..-1].each { |a|\r
377         arg = a.to_sym\r
378         if bools.include?(arg)\r
379           set_prop(butarget, arg, cmd.to_sym == :enable)\r
380           things << a\r
381         else\r
382           skipped << a\r
383         end\r
384       }\r
385 \r
386       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?\r
387       if things.empty?\r
388         m.reply "I haven't changed anything"\r
389       else\r
390         @bot.auth.set_changed\r
391         return auth_manage_user(m, {:data => ["show"] + things + ["for", butarget.username] })\r
392       end\r
393 \r
394     when :set\r
395       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
396       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
397 \r
398       return m.reply(need_args(cmd)) unless splits[1]\r
399       arg = splits[1].to_sym\r
400       return m.reply(not_args(cmd, *can_set)) unless can_set.include?(arg)\r
401       argarg = splits[2]\r
402       return m.reply(need_args([cmd, splits[1]].join(" "))) unless argarg\r
403       if arg == :password && m.public?\r
404         return m.reply("is that a joke? setting the password in public?")\r
405       end\r
406       set_prop(butarget, arg, argarg)\r
407       @bot.auth.set_changed\r
408       auth_manage_user(m, {:data => ["show", arg, "for", butarget.username] })\r
409 \r
410     when :reset\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       return m.reply(need_args(cmd)) unless splits[1]\r
415       things = []\r
416       skipped = []\r
417       splits[1..-1].each { |a|\r
418         arg = a.to_sym\r
419         if can_reset.include?(arg)\r
420           reset_prop(butarget, arg)\r
421           things << a\r
422         else\r
423           skipped << a\r
424         end\r
425       }\r
426 \r
427       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?\r
428       if things.empty?\r
429         m.reply "I haven't changed anything"\r
430       else\r
431         @bot.auth.set_changed\r
432         @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")\r
433         return auth_manage_user(m, {:data => (["show"] + things - ["password"]) + ["for", butarget.username]})\r
434       end\r
435 \r
436     when :add, :rm, :remove, :del, :delete\r
437       return m.reply("you can't change the default user") if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
438       return m.reply("you can't edit #{butarget.username}") if butarget != botuser and !botuser.permit?("auth::edit::other")\r
439 \r
440       arg = splits[1]\r
441       if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?\r
442         return m.reply("I can only add/remove netmasks. See +help user add+ for more instructions")\r
443       end\r
444 \r
445       method = cmd.to_sym == :add ? :add_netmask : :delete_netmask\r
446 \r
447       failed = []\r
448 \r
449       splits[2..-1].each { |mask|\r
450         begin\r
451           butarget.send(method, mask.to_irc_netmask(:server => @bot.server))\r
452         rescue\r
453           failed << mask\r
454         end\r
455       }\r
456       m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?\r
457       @bot.auth.set_changed\r
458       return auth_manage_user(m, {:data => ["show", "netmasks", "for", butarget.username] })\r
459 \r
460     else\r
461       m.reply "sorry, I don't know how to #{m.message}"\r
462     end\r
463   end\r
464 \r
465   def auth_tell_password(m, params)\r
466     user = params[:user]\r
467     begin\r
468       botuser = @bot.auth.get_botuser(params[:botuser])\r
469     rescue\r
470       return m.reply("coudln't find botuser #{params[:botuser]})")\r
471     end\r
472     m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner\r
473     msg = "the password for botuser #{botuser.username} is #{botuser.password}"\r
474     @bot.say user, msg\r
475     @bot.say m.source, "I told #{user} that " + msg\r
476   end\r
477 \r
478   def auth_create_user(m, params)\r
479     name = params[:name]\r
480     password = params[:password]\r
481     return m.reply("are you nuts, creating a botuser with a publicly known password?") if m.public? and not password.nil?\r
482     begin\r
483       bu = @bot.auth.create_botuser(name, password)\r
484       @bot.auth.set_changed\r
485     rescue => e\r
486       m.reply "failed to create #{name}: #{e}"\r
487       debug e.inspect + "\n" + e.backtrace.join("\n")\r
488       return\r
489     end\r
490     m.reply "created botuser #{bu.username}"\r
491   end\r
492 \r
493   def auth_list_users(m, params)\r
494     # TODO name regexp to filter results\r
495     list = @bot.auth.save_array.inject([]) { |list, x| list << x[:username] } - ['everyone', 'owner']\r
496     if defined?(@destroy_q)\r
497       list.map! { |x|\r
498         @destroy_q.include?(x) ? x + " (queued for destruction)" : x\r
499       }\r
500     end\r
501     return m.reply("I have no botusers other than the default ones") if list.empty?\r
502     return m.reply("botuser#{'s' if list.length > 1}: #{list.join(', ')}")\r
503   end\r
504 \r
505   def auth_destroy_user(m, params)\r
506     @destroy_q = [] unless defined?(@destroy_q)\r
507     buname = params[:name]\r
508     return m.reply("You can't destroy #{buname}") if ["everyone", "owner"].include?(buname)\r
509     cancel = m.message.split[1] == 'cancel'\r
510     password = params[:password]\r
511 \r
512     buser_array = @bot.auth.save_array\r
513     buser_hash = buser_array.inject({}) { |h, u|\r
514       h[u[:username]] = u\r
515       h\r
516     }\r
517 \r
518     return m.reply("no such botuser #{buname}") unless buser_hash.keys.include?(buname)\r
519 \r
520     if cancel\r
521       if @destroy_q.include?(buname)\r
522         @destroy_q.delete(buname)\r
523         m.reply "#{buname} removed from the destruction queue"\r
524       else\r
525         m.reply "#{buname} was not queued for destruction"\r
526       end\r
527       return\r
528     end\r
529 \r
530     if password.nil?\r
531       if @destroy_q.include?(buname)\r
532         rep = "#{buname} already queued for destruction"\r
533       else\r
534         @destroy_q << buname\r
535         rep = "#{buname} queued for destruction"\r
536       end\r
537       return m.reply(rep + ", use #{Bold}user destroy #{buname} <password>#{Bold} to destroy it")\r
538     else\r
539       begin\r
540         return m.reply("#{buname} is not queued for destruction yet") unless @destroy_q.include?(buname)\r
541         return m.reply("wrong password for #{buname}") unless buser_hash[buname][:password] == password\r
542         buser_array.delete_if { |u|\r
543           u[:username] == buname\r
544         }\r
545         @destroy_q.delete(buname)\r
546         @bot.auth.load_array(buser_array, true)\r
547         @bot.auth.set_changed\r
548       rescue => e\r
549         return m.reply("failed: #{e}")\r
550       end\r
551       return m.reply("botuser #{buname} destroyed")\r
552     end\r
553 \r
554   end\r
555 \r
556   def auth_copy_ren_user(m, params)\r
557     source = Auth::BotUser.sanitize_username(params[:source])\r
558     dest = Auth::BotUser.sanitize_username(params[:dest])\r
559     return m.reply("please don't touch the default users") if (["everyone", "owner"] | [source, dest]).length < 4\r
560 \r
561     buser_array = @bot.auth.save_array\r
562     buser_hash = buser_array.inject({}) { |h, u|\r
563       h[u[:username]] = u\r
564       h\r
565     }\r
566 \r
567     return m.reply("no such botuser #{source}") unless buser_hash.keys.include?(source)\r
568     return m.reply("botuser #{dest} exists already") if buser_hash.keys.include?(dest)\r
569 \r
570     copying = m.message.split[1] == "copy"\r
571     begin\r
572       if copying\r
573         h = {}\r
574         buser_hash[source].each { |k, val|\r
575           h[k] = val.dup\r
576         }\r
577       else\r
578         h = buser_hash[source]\r
579       end\r
580       h[:username] = dest\r
581       buser_array << h if copying\r
582 \r
583       @bot.auth.load_array(buser_array, true)\r
584       @bot.auth.set_changed\r
585     rescue => e\r
586       return m.reply("failed: #{e}")\r
587     end\r
588     return m.reply("botuser #{source} copied to #{dest}") if copying\r
589     return m.reply("botuser #{source} renamed to #{dest}")\r
590 \r
591   end\r
592 \r
593   def auth_export(m, params)\r
594 \r
595     exportfile = "#{@bot.botclass}/new-auth.users"\r
596 \r
597     what = params[:things]\r
598 \r
599     has_to = what[-2] == "to"\r
600     if has_to\r
601       exportfile = "#{@bot.botclass}/#{what[-1]}"\r
602       what.slice!(-2,2)\r
603     end\r
604 \r
605     what.delete("all")\r
606 \r
607     m.reply "selecting data to export ..."\r
608 \r
609     buser_array = @bot.auth.save_array\r
610     buser_hash = buser_array.inject({}) { |h, u|\r
611       h[u[:username]] = u\r
612       h\r
613     }\r
614 \r
615     if what.empty?\r
616       we_want = buser_hash\r
617     else\r
618       we_want = buser_hash.delete_if { |key, val|\r
619         not what.include?(key)\r
620       }\r
621     end\r
622 \r
623     m.reply "preparing data for export ..."\r
624     begin\r
625       yaml_hash = {}\r
626       we_want.each { |k, val|\r
627         yaml_hash[k] = {}\r
628         val.each { |kk, v|\r
629           case kk\r
630           when :username\r
631             next\r
632           when :netmasks\r
633             yaml_hash[k][kk] = []\r
634             v.each { |nm|\r
635               yaml_hash[k][kk] << {\r
636                 :fullform => nm.fullform,\r
637                 :casemap => nm.casemap.to_s\r
638               }\r
639             }\r
640           else\r
641             yaml_hash[k][kk] = v\r
642           end\r
643         }\r
644       }\r
645     rescue => e\r
646       m.reply "failed to prepare data: #{e}"\r
647       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
648       return\r
649     end\r
650 \r
651     m.reply "exporting to #{exportfile} ..."\r
652     begin\r
653       # m.reply yaml_hash.inspect\r
654       File.open(exportfile, "w") do |file|\r
655         file.puts YAML::dump(yaml_hash)\r
656       end\r
657     rescue => e\r
658       m.reply "failed to export users: #{e}"\r
659       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
660       return\r
661     end\r
662     m.reply "done"\r
663   end\r
664 \r
665   def auth_import(m, params)\r
666 \r
667     importfile = "#{@bot.botclass}/new-auth.users"\r
668 \r
669     what = params[:things]\r
670 \r
671     has_from = what[-2] == "from"\r
672     if has_from\r
673       importfile = "#{@bot.botclass}/#{what[-1]}"\r
674       what.slice!(-2,2)\r
675     end\r
676 \r
677     what.delete("all")\r
678 \r
679     m.reply "reading #{importfile} ..."\r
680     begin\r
681       yaml_hash = YAML::load_file(importfile)\r
682     rescue => e\r
683       m.reply "failed to import from: #{e}"\r
684       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
685       return\r
686     end\r
687 \r
688     # m.reply yaml_hash.inspect\r
689 \r
690     m.reply "selecting data to import ..."\r
691 \r
692     if what.empty?\r
693       we_want = yaml_hash\r
694     else\r
695       we_want = yaml_hash.delete_if { |key, val|\r
696         not what.include?(key)\r
697       }\r
698     end\r
699 \r
700     m.reply "parsing data from import ..."\r
701 \r
702     buser_hash = {}\r
703 \r
704     begin\r
705       yaml_hash.each { |k, val|\r
706         buser_hash[k] = { :username => k }\r
707         val.each { |kk, v|\r
708           case kk\r
709           when :netmasks\r
710             buser_hash[k][kk] = []\r
711             v.each { |nm|\r
712               buser_hash[k][kk] << nm[:fullform].to_irc_netmask(:casemap => nm[:casemap].to_irc_casemap).to_irc_netmask(:server => @bot.server)\r
713             }\r
714           else\r
715             buser_hash[k][kk] = v\r
716           end\r
717         }\r
718       }\r
719     rescue => e\r
720       m.reply "failed to parse data: #{e}"\r
721       debug e.backtrace.dup.unshift(e.inspect).join("\n")\r
722       return\r
723     end\r
724 \r
725     # m.reply buser_hash.inspect\r
726 \r
727     org_buser_array = @bot.auth.save_array\r
728     org_buser_hash = org_buser_array.inject({}) { |h, u|\r
729       h[u[:username]] = u\r
730       h\r
731     }\r
732 \r
733     # TODO we may want to do a(n optional) key-by-key merge\r
734     #\r
735     org_buser_hash.merge!(buser_hash)\r
736     new_buser_array = org_buser_hash.values\r
737     @bot.auth.load_array(new_buser_array, true)\r
738     @bot.auth.set_changed\r
739 \r
740     m.reply "done"\r
741   end\r
742 \r
743 end\r
744 \r
745 auth = AuthModule.new\r
746 \r
747 auth.map "user export *things",\r
748   :action => 'auth_export',\r
749   :defaults => { :things => ['all'] },\r
750   :auth_path => ':manage:fedex:'\r
751 \r
752 auth.map "user import *things",\r
753  :action => 'auth_import',\r
754  :auth_path => ':manage:fedex:'\r
755 \r
756 auth.map "user create :name :password",\r
757   :action => 'auth_create_user',\r
758   :defaults => {:password => nil},\r
759   :auth_path => ':manage:'\r
760 \r
761 auth.map "user [cancel] destroy :name :password",\r
762   :action => 'auth_destroy_user',\r
763   :defaults => { :password => nil },\r
764   :auth_path => ':manage::destroy:'\r
765 \r
766 auth.map "user copy :source [to] :dest",\r
767   :action => 'auth_copy_ren_user',\r
768   :auth_path => ':manage:'\r
769 \r
770 auth.map "user rename :source [to] :dest",\r
771   :action => 'auth_copy_ren_user',\r
772   :auth_path => ':manage:'\r
773 \r
774 auth.default_auth("user::manage", false)\r
775 \r
776 auth.map "user tell :user the password for :botuser",\r
777   :action => 'auth_tell_password',\r
778   :auth_path => '::'\r
779 \r
780 auth.map "user list",\r
781   :action => 'auth_list_users',\r
782   :auth_path => '::'\r
783 \r
784 auth.map "user *data",\r
785   :action => 'auth_manage_user'\r
786 \r
787 auth.default_auth("user", true)\r
788 auth.default_auth("edit::other", false)\r
789 \r
790 auth.map "whoami",\r
791   :action => 'auth_whoami',\r
792   :auth_path => '!*!'\r
793 \r
794 auth.map "auth :password",\r
795   :action => 'auth_auth',\r
796   :public => false,\r
797   :auth_path => '!login!'\r
798 \r
799 auth.map "login :botuser :password",\r
800   :action => 'auth_login',\r
801   :public => false,\r
802   :defaults => { :password => nil },\r
803   :auth_path => '!login!'\r
804 \r
805 auth.map "login :botuser",\r
806   :action => 'auth_login',\r
807   :auth_path => '!login!'\r
808 \r
809 auth.map "login",\r
810   :action => 'auth_autologin',\r
811   :auth_path => '!login!'\r
812 \r
813 auth.map "permissions set *args",\r
814   :action => 'auth_edit_perm',\r
815   :auth_path => ':edit::set:'\r
816 \r
817 auth.map "permissions reset *args",\r
818   :action => 'auth_edit_perm',\r
819   :auth_path => ':edit::reset:'\r
820 \r
821 auth.map "permissions view [for :user]",\r
822   :action => 'auth_view_perm',\r
823   :auth_path => '::'\r
824 \r
825 auth.default_auth('*', false)\r
826 \r