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