]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/auth.rb
User management is now almost complete. The only missing functionality is the creatio...
[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("please do not use + or - in front of command #{x} when resetting") unless setting\r
58         else\r
59           warns << ArgumentError("+ 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 wants_more\r
76           last_idx = i\r
77           break\r
78         end\r
79       end\r
80     }\r
81     warns << "trailing comma" if wants_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_set(m, params)\r
87     cmds, locs, warns = parse_args(params[:args])\r
88     errs = warns.select { |w| w.kind_of?(Exception) }\r
89     unless errs.empty?\r
90       m.reply "couldn't satisfy your request: #{errs.join(',')}"\r
91       return\r
92     end\r
93     user = params[:user].sub(/^all$/,"everyone")\r
94     begin\r
95       bu = @bot.auth.get_botuser(user)\r
96     rescue\r
97       m.reply "couldn't find botuser #{user}"\r
98       return\r
99     end\r
100     if locs.empty?\r
101       locs << "*"\r
102     end\r
103     begin\r
104       locs.each { |loc|\r
105         ch = loc\r
106         if m.private?\r
107           ch = "?" if loc == "_"\r
108         else\r
109           ch = m.target.to_s if loc == "_"\r
110         end\r
111         cmds.each { |setval|\r
112           val = setval[0].chr == '+'\r
113           cmd = setval[1..-1]\r
114           bu.set_permission(cmd, val, ch)\r
115         }\r
116       }\r
117     rescue => e\r
118       m.reply "Something went wrong while trying to set the permissions"\r
119       raise\r
120     end\r
121     @bot.auth.set_changed\r
122     debug "User #{user} permissions changed"\r
123     m.reply "Ok, #{user} now also has permissions #{params[:args].join(' ')}"\r
124   end\r
125 \r
126   def get_botuser_for(user)\r
127     @bot.auth.irc_to_botuser(user)\r
128   end\r
129 \r
130   def get_botusername_for(user)\r
131     get_botuser_for(user).username\r
132   end\r
133 \r
134   def welcome(user)\r
135     "welcome, #{get_botusername_for(user)}"\r
136   end\r
137 \r
138   def auth_login(m, params)\r
139     begin\r
140       case @bot.auth.login(m.source, params[:botuser], params[:password])\r
141       when true\r
142         m.reply welcome(m.source)\r
143         @bot.auth.set_changed\r
144       else\r
145         m.reply "sorry, can't do"\r
146       end\r
147     rescue => e\r
148       m.reply "couldn't login: #{e}"\r
149       raise\r
150     end\r
151   end\r
152 \r
153   def auth_autologin(m, params)\r
154     u = do_autologin(m.source)\r
155     case u.username\r
156     when 'everyone'\r
157       m.reply "I couldn't find anything to let you login automatically"\r
158     else\r
159       m.reply welcome(m.source)\r
160     end\r
161   end\r
162 \r
163   def do_autologin(user)\r
164     @bot.auth.autologin(user)\r
165   end\r
166 \r
167   def auth_whoami(m, params)\r
168     rep = ""\r
169     # if m.public?\r
170     #   rep << m.source.nick << ", "\r
171     # end\r
172     rep << "you are "\r
173     rep << get_botusername_for(m.source).gsub(/^everyone$/, "no one that I know").gsub(/^owner$/, "my boss")\r
174     m.reply rep\r
175   end\r
176 \r
177   def help(plugin, topic="")\r
178     case topic\r
179     when /^login/\r
180       return "login [<botuser>] [<pass>]: logs in to the bot as botuser <botuser> with password <pass>. <pass> can be omitted if <botuser> allows login-by-mask and your netmask is among the known ones. if <botuser> is omitted too autologin will be attempted"\r
181     when /^whoami/\r
182       return "whoami: names the botuser you're linked to"\r
183     when /^permission syntax/\r
184       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
185     when /^permission/\r
186       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
187     when /^user (show|list)/\r
188       return "user show <what> : shows info about the user; <what> can be any of autologin, login-by-mask, netmasks"\r
189     when /^user (en|dis)able/\r
190       return "user enable|disable <what> : turns on or off <what> (autologin, login-by-mask)"\r
191     when /^user set/\r
192       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
193     when /^user (add|rm)/\r
194       return "user add|rm netmask <mask> : adds/removes netmask <mask> from the list of netmasks known to the botuser you're linked to"\r
195     when /^user reset/\r
196       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
197     when /^user tell/\r
198       return "user tell <who> the password for <botuser> : contacts <who> in private to tell him/her the password for <botuser>"\r
199     when /^user/\r
200       return "user show|list, enable|disable, add|rm netmask, set, reset, tell"\r
201     else\r
202       return "#{name}: login, whoami, permission syntax, permissions, user"\r
203     end\r
204   end\r
205 \r
206   def need_args(cmd)\r
207     "sorry, I need more arguments to #{cmd}"\r
208   end\r
209 \r
210   def not_args(cmd, *stuff)\r
211     "I can only #{cmd} these: #{stuff.join(', ')}"\r
212   end\r
213 \r
214   def set_prop(botuser, prop, val)\r
215     k = prop.to_s.gsub("-","_")\r
216     botuser.send( (k + "=").to_sym, val)\r
217   end\r
218 \r
219   def reset_prop(botuser, prop)\r
220     k = prop.to_s.gsub("-","_")\r
221     botuser.send( ("reset_"+k).to_sym)\r
222   end\r
223 \r
224   def ask_bool_prop(botuser, prop)\r
225     k = prop.to_s.gsub("-","_")\r
226     botuser.send( (k + "?").to_sym)\r
227   end\r
228 \r
229   def auth_manage_user(m, params)\r
230     splits = params[:data]\r
231 \r
232     cmd = splits.first\r
233     return auth_whoami(m, params) if cmd.nil?\r
234 \r
235     botuser = get_botuser_for(m.source)\r
236     # By default, we do stuff on the botuser the irc user is bound to\r
237     butarget = botuser\r
238 \r
239     has_for = splits[-2] == "for"\r
240     butarget = @bot.auth.get_botuser(splits[-1]) if has_for\r
241     return m.reply "you can't mess with #{butarget.username}" if butarget == @bot.auth.botowner && botuser != butarget\r
242     splits.slice!(-2,2) if has_for\r
243 \r
244     bools = [:autologin, :"login-by-mask"]\r
245     can_set = [:password]\r
246     can_addrm = [:netmasks]\r
247     can_reset = bools + can_set + can_addrm\r
248 \r
249     case cmd.to_sym\r
250 \r
251     when :show, :list\r
252       return "you can't see the properties of #{butarget.username}" if botuser != butarget and !botuser.permit?("auth::show::other")\r
253 \r
254       case splits[1]\r
255       when nil, "all"\r
256         props = can_reset\r
257       when "password"\r
258         if botuser != butarget\r
259           return m.reply "no way I'm telling you the master password!" if butarget == @bot.auth.botowner\r
260           return m.reply "you can't ask for someone else's password"\r
261         end\r
262         return m.reply "c'mon, you can't be asking me seriously to tell you the password in public!" if m.public?\r
263         return m.reply "the password for #{butarget.username} is #{butarget.password}"\r
264       else\r
265         props = splits[1..-1]\r
266       end\r
267 \r
268       str = []\r
269 \r
270       props.each { |arg|\r
271         k = arg.to_sym\r
272         next if k == :password\r
273         case k\r
274         when *bools\r
275           str << "can"\r
276           str.last << "not" unless ask_bool_prop(butarget, k)\r
277           str.last << " #{k}"\r
278         when :netmasks\r
279           str << "knows "\r
280           if butarget.netmasks.empty?\r
281             str.last << "no netmasks"\r
282           else\r
283             str.last << butarget.netmasks.join(", ")\r
284           end\r
285         end\r
286       }\r
287       return m.reply "#{butarget.username} #{str.join('; ')}"\r
288 \r
289     when :enable, :disable\r
290       return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::other::default")\r
291       return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")\r
292 \r
293       return m.reply need_args(cmd) unless splits[1]\r
294       things = []\r
295       skipped = []\r
296       splits[1..-1].each { |a|\r
297         arg = a.to_sym\r
298         if bools.include?(arg)\r
299           set_prop(butarget, arg, cmd.to_sym == :enable)\r
300           things << a\r
301         else\r
302           skipped << a\r
303         end\r
304       }\r
305 \r
306       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *bools) unless skipped.empty?\r
307       if things.empty?\r
308         m.reply "I haven't changed anything"\r
309       else\r
310         @bot.auth.set_changed\r
311         return auth_manage_user(m, {:data => ["show"] + things })\r
312       end\r
313 \r
314     when :set\r
315       return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
316       return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")\r
317 \r
318       return m.reply need_args(cmd) unless splits[1]\r
319       arg = splits[1].to_sym\r
320       return m.reply not_args(cmd, *can_set) unless can_set.include?(arg)\r
321       argarg = splits[2]\r
322       return m.reply need_args([cmd, splits[1]].join(" ")) unless argarg\r
323       if arg == :password && m.public?\r
324         return m.reply "is that a joke? setting the password in public?"\r
325       end\r
326       set_prop(butarget, arg, argarg)\r
327       @bot.auth.set_changed\r
328       auth_manage_user(m, {:data => ["show", arg] })\r
329 \r
330     when :reset\r
331       return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
332       return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")\r
333 \r
334       return m.reply need_args(cmd) unless splits[1]\r
335       things = []\r
336       skipped = []\r
337       splits[1..-1].each { |a|\r
338         arg = a.to_sym\r
339         if can_reset.include?(arg)\r
340           reset_prop(butarget, arg)\r
341           things << a\r
342         else\r
343           skipped << a\r
344         end\r
345       }\r
346 \r
347       m.reply "I ignored #{skipped.join(', ')} because " + not_args(cmd, *can_reset) unless skipped.empty?\r
348       if things.empty?\r
349         m.reply "I haven't changed anything"\r
350       else\r
351         @bot.auth.set_changed\r
352         @bot.say m.source, "the password for #{butarget.username} is now #{butarget.password}" if things.include?("password")\r
353         return auth_manage_user(m, {:data => ["show"] + things - ["password"]})\r
354       end\r
355 \r
356     when :add, :rm, :remove, :del, :delete\r
357       return m.reply "you can't change the default user" if butarget == @bot.auth.everyone and !botuser.permit?("auth::edit::default")\r
358       return m.reply "you can't edit #{butarget.username}" if butarget != botuser and !botuser.permit?("auth::edit::other")\r
359 \r
360       arg = splits[1]\r
361       if arg.nil? or arg !~ /netmasks?/ or splits[2].nil?\r
362         return m.reply "I can only add/remove netmasks. See +help user add+ for more instructions"\r
363       end\r
364 \r
365       method = cmd.to_sym == :add ? :add_netmask : :delete_netmask\r
366 \r
367       failed = []\r
368 \r
369       splits[2..-1].each { |mask|\r
370         begin\r
371           butarget.send(method, mask.to_irc_netmask(:server => @bot.server))\r
372         rescue\r
373           failed << mask\r
374         end\r
375       }\r
376       m.reply "I failed to #{cmd} #{failed.join(', ')}" unless failed.empty?\r
377       @bot.auth.set_changed\r
378       return auth_manage_user(m, {:data => ["show", "netmasks"] })\r
379 \r
380     else\r
381       m.reply "sorry, I don't know how to #{m.message}"\r
382     end\r
383   end\r
384 \r
385   def auth_tell_password(m, params)\r
386     user = params[:user]\r
387     botuser = params[:botuser]\r
388     m.reply "I'm not telling the master password to anyway, pal" if botuser == @bot.auth.botowner\r
389     msg = "the password for #{botuser.username} is #{botuser.password}"\r
390     @bot.say user, msg\r
391     @bot.say m.source, "I told #{user} that " + msg\r
392   end\r
393 \r
394 end\r
395 \r
396 auth = AuthModule.new\r
397 \r
398 auth.map "user tell :user the password for :botuser",\r
399   :action => 'auth_tell_password',\r
400   :auth_path => 'user::tell'\r
401 \r
402 auth.map "user *data",\r
403   :action => 'auth_manage_user'\r
404 \r
405 auth.default_auth("user", true)\r
406 auth.default_auth("edit::other", false)\r
407 \r
408 auth.map "whoami",\r
409   :action => 'auth_whoami',\r
410   :auth_path => '!*!'\r
411 \r
412 auth.map "login :botuser :password",\r
413   :action => 'auth_login',\r
414   :public => false,\r
415   :defaults => { :password => nil },\r
416   :auth_path => '!login!'\r
417 \r
418 auth.map "login :botuser",\r
419   :action => 'auth_login',\r
420   :auth_path => '!login!'\r
421 \r
422 auth.map "login",\r
423   :action => 'auth_autologin',\r
424   :auth_path => '!login!'\r
425 \r
426 auth.map "permissions set *args for :user",\r
427   :action => 'auth_set',\r
428   :auth_path => ':edit::set:'\r
429 \r
430 auth.map "permissions reset *args for :user",\r
431   :action => 'auth_reset',\r
432   :auth_path => ':edit::reset:'\r
433 \r
434 auth.default_auth('*', false)\r
435 \r