/usr/share/pyshared/jabberbot/xmlrpcbot.py is in python-moinmoin 1.9.3-1ubuntu2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 | # -*- coding: iso-8859-1 -*-
"""
MoinMoin - a xmlrpc server and client for the notification bot
@copyright: 2007 by Karol Nowak <grywacz@gmail.com>
@license: GNU GPL, see COPYING for details.
"""
import logging, xmlrpclib, Queue
from SimpleXMLRPCServer import SimpleXMLRPCServer
from threading import Thread
import jabberbot.commands as cmd
class ConfigurationError(Exception):
def __init__(self, message):
Exception.__init__(self)
self.message = message
def _xmlrpc_decorator(function):
"""A decorator function, which adds some maintenance code
This function takes care of preparing a MultiCall object and
an authentication token, and deleting them at the end.
"""
def wrapped_func(self, command):
# Dummy function, so that the string appears in a .po file
_ = lambda x: x
self.token = None
self.multicall = xmlrpclib.MultiCall(self.connection)
jid = command.jid
if type(jid) is not list:
jid = [jid]
try:
try:
self.get_auth_token(command.jid)
if self.token:
self.multicall.applyAuthToken(self.token)
function(self, command)
self.commands_out.put_nowait(command)
except xmlrpclib.Fault, fault:
msg = _("Your request has failed. The reason is:\n%(error)s")
self.log.error(str(fault))
self.report_error(jid, msg, {'error': fault.faultString})
except xmlrpclib.Error, err:
msg = _("A serious error occured while processing your request:\n%(error)s")
self.log.error(str(err))
self.report_error(jid, msg, {'error': str(err)})
except Exception, exc:
msg = _("An internal error has occured, please contact the administrator.")
self.log.critical(str(exc))
self.report_error(jid, msg)
finally:
del self.token
del self.multicall
return wrapped_func
class XMLRPCClient(Thread):
"""XMLRPC Client
It's responsible for performing XMLRPC operations on
a wiki, as inctructed by command objects received from
the XMPP component"""
def __init__(self, config, commands_in, commands_out):
"""A constructor
@param commands_out: an output command queue (to xmpp)
@param commands_in: an input command queue (from xmpp)
"""
Thread.__init__(self)
self.log = logging.getLogger(__name__)
if not config.secret:
error = "You must set a (long) secret string!"
self.log.critical(error)
raise ConfigurationError(error)
self.commands_in = commands_in
self.commands_out = commands_out
self.config = config
self.url = config.wiki_url + "?action=xmlrpc2"
self.connection = self.create_connection()
self.token = None
self.multicall = None
self.stopping = False
self._cmd_handlers = {cmd.GetPage: self.get_page,
cmd.GetPageHTML: self.get_page_html,
cmd.GetPageList: self.get_page_list,
cmd.GetPageInfo: self.get_page_info,
cmd.GetUserLanguage: self.get_language_by_jid,
cmd.Search: self.do_search,
cmd.RevertPage: self.do_revert}
def run(self):
"""Starts the server / thread"""
while True:
if self.stopping:
break
try:
command = self.commands_in.get(True, 2)
self.execute_command(command)
except Queue.Empty:
pass
def stop(self):
"""Stop the thread"""
self.stopping = True
def create_connection(self):
return xmlrpclib.ServerProxy(self.url, allow_none=True, verbose=self.config.verbose)
def execute_command(self, command):
"""Execute commands coming from the XMPP component"""
cmd_name = command.__class__
try:
handler = self._cmd_handlers[cmd_name]
except KeyError:
self.log.debug("No such command: " + cmd_name.__name__)
return
handler(command)
def report_error(self, jid, text, data={}):
"""Reports an internal error
@param jid: Jabber ID that should be informed about the error condition
@param text: description of the error
@param data: dictionary used to substitute strings in translated message
@type data: dict
"""
# Dummy function, so that the string appears in a .po file
_ = lambda x: x
cmddata = {'text': text, 'data': data}
report = cmd.NotificationCommandI18n(jid, cmddata, msg_type=u"chat", async=False)
self.commands_out.put_nowait(report)
def get_auth_token(self, jid):
"""Get an auth token using user's Jabber ID
@type jid: unicode
"""
# We have to use a bare JID
jid = jid.split('/')[0]
token = self.connection.getJabberAuthToken(jid, self.config.secret)
if token:
self.token = token
def warn_no_credentials(self, jid):
"""Warn a given JID that credentials check failed
@param jid: full JID to notify about failure
@type jid: str
"""
# Dummy function, so that the string appears in a .po file
_ = lambda x: x
cmddata = {'text': _("Credentials check failed, you might be unable to see all information.")}
warning = cmd.NotificationCommandI18n([jid], cmddata, async=False)
self.commands_out.put_nowait(warning)
def _get_multicall_result(self, jid):
"""Returns multicall results and issues a warning if there's an auth error
@param jid: a full JID to use if there's an error
@type jid: str
"""
if not self.token:
result = self.multicall()[0]
token_result = u"FAILURE"
else:
token_result, result = self.multicall()
if token_result != u"SUCCESS":
self.warn_no_credentials(jid)
return result
def get_page(self, command):
"""Returns a raw page"""
self.multicall.getPage(command.pagename)
command.data = self._get_multicall_result(command.jid)
get_page = _xmlrpc_decorator(get_page)
def get_page_html(self, command):
"""Returns a html-formatted page"""
self.multicall.getPageHTML(command.pagename)
command.data = self._get_multicall_result(command.jid)
get_page_html = _xmlrpc_decorator(get_page_html)
def get_page_list(self, command):
"""Returns a list of all accesible pages"""
# Dummy function, so that the string appears in a .po file
_ = lambda x: x
cmd_data = {'text': _("This command may take a while to complete, please be patient...")}
info = cmd.NotificationCommandI18n([command.jid], cmd_data, async=False, msg_type=u"chat")
self.commands_out.put_nowait(info)
self.multicall.getAllPages()
command.data = self._get_multicall_result(command.jid)
get_page_list = _xmlrpc_decorator(get_page_list)
def get_page_info(self, command):
"""Returns detailed information about a given page"""
self.multicall.getPageInfo(command.pagename)
command.data = self._get_multicall_result(command.jid)
get_page_info = _xmlrpc_decorator(get_page_info)
def do_search(self, command):
"""Performs a search"""
# Dummy function, so that the string appears in a .po file
_ = lambda x: x
cmd_data = {'text': _("This command may take a while to complete, please be patient...")}
info = cmd.NotificationCommandI18n([command.jid], cmd_data, async=False, msg_type=u"chat")
self.commands_out.put_nowait(info)
c = command
self.multicall.searchPagesEx(c.term, c.search_type, 30, c.case, c.mtime, c.regexp)
command.data = self._get_multicall_result(command.jid)
do_search = _xmlrpc_decorator(do_search)
def do_revert(self, command):
"""Performs a page revert"""
# Dummy function, so that the string appears in a .po file
_ = lambda x: x
self.multicall.revertPage(command.pagename, command.revision)
data = self._get_multicall_result(command.jid)
if type(data) == bool and data:
cmd_data = {'text': _("Page has been reverted.")}
elif isinstance(str, data) or isinstance(unicode, data):
cmd_data = {'text': _("Revert failed: %(reason)s" % {'reason': data})}
else:
cmd_data = {'text': _("Revert failed.")}
info = cmd.NotificationCommandI18n([command.jid], cmd_data, async=False, msg_type=u"chat")
self.commands_out.put_nowait(info)
do_revert = _xmlrpc_decorator(do_revert)
def get_language_by_jid(self, command):
"""Returns language of the a user identified by the given JID"""
server = xmlrpclib.ServerProxy(self.config.wiki_url + "?action=xmlrpc2")
language = "en"
try:
language = server.getUserLanguageByJID(command.jid)
except xmlrpclib.Fault, fault:
self.log.error(str(fault))
except xmlrpclib.Error, err:
self.log.error(str(err))
except Exception, exc:
self.log.critical(str(exc))
command.language = language
self.commands_out.put_nowait(command)
class XMLRPCServer(Thread):
"""XMLRPC Server
It waits for notifications requests coming from wiki,
creates command objects and puts them on a queue for
later processing by the XMPP component
@param commands: an input command queue
"""
def __init__(self, config, commands):
Thread.__init__(self)
self.commands = commands
self.verbose = config.verbose
self.log = logging.getLogger(__name__)
self.config = config
if config.secret:
self.secret = config.secret
else:
error = "You must set a (long) secret string"
self.log.critical(error)
raise ConfigurationError(error)
self.server = None
def run(self):
"""Starts the server / thread"""
self.server = SimpleXMLRPCServer((self.config.xmlrpc_host, self.config.xmlrpc_port))
# Register methods having an "export" attribute as XML RPC functions and
# decorate them with a check for a shared (wiki-bot) secret.
items = self.__class__.__dict__.items()
methods = [(name, func) for (name, func) in items if callable(func)
and "export" in func.__dict__]
for name, func in methods:
self.server.register_function(self.secret_check(func), name)
self.server.serve_forever()
def secret_check(self, function):
"""Adds a check for a secret to a given function
Using this one does not have to worry about checking for the secret
in every XML RPC function.
"""
def protected_func(secret, *args):
if secret != self.secret:
raise xmlrpclib.Fault(1, "You are not allowed to use this bot!")
else:
return function(self, *args)
return protected_func
def send_notification(self, jids, notification):
"""Instructs the XMPP component to send a notification
The notification dict has following entries:
'text' - notification text (REQUIRED)
'subject' - notification subject
'url_list' - a list of dicts describing attached URLs
@param jids: a list of JIDs to send a message to (bare JIDs)
@type jids: a list of str or unicode
@param notification: dictionary with notification data
@type notification: dict
"""
command = cmd.NotificationCommand(jids, notification, async=True)
self.commands.put_nowait(command)
return True
send_notification.export = True
def addJIDToRoster(self, jid):
"""Instructs the XMPP component to add a new JID to its roster
@param jid: a jid to add, this must be a bare jid
@type jid: str or unicode,
"""
command = cmd.AddJIDToRosterCommand(jid)
self.commands.put_nowait(command)
return True
addJIDToRoster.export = True
def removeJIDFromRoster(self, jid):
"""Instructs the XMPP component to remove a JID from its roster
@param jid: a jid to remove, this must be a bare jid
@type jid: str or unicode
"""
command = cmd.RemoveJIDFromRosterCommand(jid)
self.commands.put_nowait(command)
return True
removeJIDFromRoster.export = True
|