Mercurial > libervia-web
comparison libervia/server/server.py @ 1128:6414fd795df4
server, pages: multi-sites refactoring:
Libervia is now handling external sites (i.e. other sites than Libervia official site).
The external site are declared in sites_path_public_dict (in [DEFAULT] section) which is read by template engine, then they are linked to virtual host with vhosts_dict (linking host name to site name) in [libervia] section.
Sites are only instanced once, so adding an alias is just a matter of mapping the alias host name in vhosts_dict with the same site name.
menu_json and url_redirections_dict can now accept keys named after site name, which will be linked to the data for the site. Data for default site can still be keyed at first level.
Libervia official pages are added to external site (if pages are not overriden), allowing to call pages of the framework and to have facilities like login handling.
Deprecated url_redirections_profile option has been removed.
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 14 Sep 2018 21:41:28 +0200 |
parents | 9234f29053b0 |
children | e6fe914c3eaf |
comparison
equal
deleted
inserted
replaced
1127:9234f29053b0 | 1128:6414fd795df4 |
---|---|
22 from twisted.web import server | 22 from twisted.web import server |
23 from twisted.web import static | 23 from twisted.web import static |
24 from twisted.web import resource as web_resource | 24 from twisted.web import resource as web_resource |
25 from twisted.web import util as web_util | 25 from twisted.web import util as web_util |
26 from twisted.web import http | 26 from twisted.web import http |
27 from twisted.web import vhost | |
27 from twisted.python.components import registerAdapter | 28 from twisted.python.components import registerAdapter |
28 from twisted.python import failure | 29 from twisted.python import failure |
29 from twisted.words.protocols.jabber import jid | 30 from twisted.words.protocols.jabber import jid |
30 | 31 |
31 from txjsonrpc.web import jsonrpc | 32 from txjsonrpc.web import jsonrpc |
32 from txjsonrpc import jsonrpclib | 33 from txjsonrpc import jsonrpclib |
33 | 34 |
34 from sat.core.log import getLogger | 35 from sat.core.log import getLogger |
35 | 36 |
36 log = getLogger(__name__) | |
37 from sat_frontends.bridge.dbus_bridge import ( | 37 from sat_frontends.bridge.dbus_bridge import ( |
38 Bridge, | 38 Bridge, |
39 BridgeExceptionNoService, | 39 BridgeExceptionNoService, |
40 const_TIMEOUT as BRIDGE_TIMEOUT, | 40 const_TIMEOUT as BRIDGE_TIMEOUT, |
41 ) | 41 ) |
42 from sat.core.i18n import _, D_ | 42 from sat.core.i18n import _, D_ |
43 from sat.core import exceptions | 43 from sat.core import exceptions |
44 from sat.tools import utils | 44 from sat.tools import utils |
45 from sat.tools.common import regex | 45 from sat.tools.common import regex |
46 from sat.tools.common import template | 46 from sat.tools.common import template |
47 from sat.tools.common import uri as common_uri | |
47 | 48 |
48 import re | 49 import re |
49 import glob | 50 import glob |
50 import os.path | 51 import os.path |
51 import sys | 52 import sys |
70 | 71 |
71 from libervia.server.constants import Const as C | 72 from libervia.server.constants import Const as C |
72 from libervia.server.blog import MicroBlog | 73 from libervia.server.blog import MicroBlog |
73 from libervia.server import session_iface | 74 from libervia.server import session_iface |
74 | 75 |
75 | 76 log = getLogger(__name__) |
76 # following value are set from twisted.plugins.libervia_server initialise (see the comment there) | 77 |
78 | |
79 # following value are set from twisted.plugins.libervia_server initialise | |
80 # (see the comment there) | |
77 DATA_DIR_DEFAULT = OPT_PARAMETERS_BOTH = OPT_PARAMETERS_CFG = coerceDataDir = None | 81 DATA_DIR_DEFAULT = OPT_PARAMETERS_BOTH = OPT_PARAMETERS_CFG = coerceDataDir = None |
78 | 82 |
79 | 83 |
80 class LiberviaSession(server.Session): | 84 class LiberviaSession(server.Session): |
81 sessionTimeout = C.SESSION_TIMEOUT | 85 sessionTimeout = C.SESSION_TIMEOUT |
98 if not self.__lock: | 102 if not self.__lock: |
99 server.Session.touch(self) | 103 server.Session.touch(self) |
100 | 104 |
101 | 105 |
102 class ProtectedFile(static.File): | 106 class ProtectedFile(static.File): |
103 """A static.File class which doens't show directory listing""" | 107 """A static.File class which doesn't show directory listing""" |
104 | 108 |
105 def directoryListing(self): | 109 def directoryListing(self): |
106 return web_resource.NoResource() | 110 return web_resource.NoResource() |
107 | 111 |
108 | 112 |
110 """Specialized resource for Libervia root | 114 """Specialized resource for Libervia root |
111 | 115 |
112 handle redirections declared in sat.conf | 116 handle redirections declared in sat.conf |
113 """ | 117 """ |
114 | 118 |
119 def __init__(self, host, host_name, site_name, site_path, *args, **kwargs): | |
120 ProtectedFile.__init__(self, *args, **kwargs) | |
121 self.host = host | |
122 self.host_name = host_name | |
123 self.site_name = site_name | |
124 self.site_path = site_path | |
125 self.named_pages = {} | |
126 self.uri_callbacks = {} | |
127 self.pages_redirects = {} | |
128 self.cached_urls = {} | |
129 self.main_menu = None | |
130 | |
131 def __unicode__(self): | |
132 return (u"Root resource for {host_name} using {site_name} at {site_path} and " | |
133 u"deserving files at {path}".format( | |
134 host_name=self.host_name, site_name=self.site_name, | |
135 site_path=self.site_path, path=self.path)) | |
136 | |
137 def __str__(self): | |
138 return self.__unicode__.encode('utf-8') | |
139 | |
115 def _initRedirections(self, options): | 140 def _initRedirections(self, options): |
141 url_redirections = options["url_redirections_dict"] | |
142 | |
143 url_redirections = url_redirections.get(self.site_name, {}) | |
144 | |
116 ## redirections | 145 ## redirections |
117 self.redirections = {} | 146 self.redirections = {} |
118 self.inv_redirections = {} # new URL to old URL map | 147 self.inv_redirections = {} # new URL to old URL map |
119 | 148 |
120 if options["url_redirections_dict"] and not options["url_redirections_profile"]: | 149 for old, new_data in url_redirections.iteritems(): |
121 # FIXME: url_redirections_profile should not be needed. It is currently used to | |
122 # redirect to an URL which associate the profile with the service, but this | |
123 # is not clean, and service should be explicitly specified | |
124 raise ValueError( | |
125 u"url_redirections_profile need to be filled if you want to use url_redirections_dict" | |
126 ) | |
127 | |
128 for old, new_data in options["url_redirections_dict"].iteritems(): | |
129 # new_data can be a dictionary or a unicode url | 150 # new_data can be a dictionary or a unicode url |
130 if isinstance(new_data, dict): | 151 if isinstance(new_data, dict): |
131 # new_data dict must contain either "url", "page" or "path" key (exclusive) | 152 # new_data dict must contain either "url", "page" or "path" key |
153 # (exclusive) | |
132 # if "path" is used, a file url is constructed with it | 154 # if "path" is used, a file url is constructed with it |
133 if len({"path", "url", "page"}.intersection(new_data.keys())) != 1: | 155 if len({"path", "url", "page"}.intersection(new_data.keys())) != 1: |
134 raise ValueError( | 156 raise ValueError( |
135 u'You must have one and only one of "url", "page" or "path" key in your url_redirections_dict data' | 157 u'You must have one and only one of "url", "page" or "path" key ' |
136 ) | 158 u'in your url_redirections_dict data') |
137 if "url" in new_data: | 159 if "url" in new_data: |
138 new = new_data["url"] | 160 new = new_data["url"] |
139 elif "page" in new_data: | 161 elif "page" in new_data: |
140 new = new_data | 162 new = new_data |
141 new["type"] = "page" | 163 new["type"] = "page" |
142 new.setdefault("path_args", []) | 164 new.setdefault("path_args", []) |
143 if not isinstance(new["path_args"], list): | 165 if not isinstance(new["path_args"], list): |
144 log.error( | 166 log.error( |
145 _( | 167 _(u'"path_args" in redirection of {old} must be a list. ' |
146 u'"path_args" in redirection of {old} must be a list. Ignoring the redirection'.format( | 168 u'Ignoring the redirection'.format(old=old))) |
147 old=old | |
148 ) | |
149 ) | |
150 ) | |
151 continue | 169 continue |
152 new.setdefault("query_args", {}) | 170 new.setdefault("query_args", {}) |
153 if not isinstance(new["query_args"], dict): | 171 if not isinstance(new["query_args"], dict): |
154 log.error( | 172 log.error( |
155 _( | 173 _( |
156 u'"query_args" in redirection of {old} must be a dictionary. Ignoring the redirection'.format( | 174 u'"query_args" in redirection of {old} must be a ' |
157 old=old | 175 u'dictionary. Ignoring the redirection'.format(old=old))) |
158 ) | |
159 ) | |
160 ) | |
161 continue | 176 continue |
162 new["path_args"] = [quote(a) for a in new["path_args"]] | 177 new["path_args"] = [quote(a) for a in new["path_args"]] |
163 # we keep an inversed dict of page redirection (page/path_args => redirecting URL) | 178 # we keep an inversed dict of page redirection |
164 # so getURL can return the redirecting URL if the same arguments are used | 179 # (page/path_args => redirecting URL) |
165 # making the URL consistent | 180 # so getURL can return the redirecting URL if the same arguments |
181 # are used # making the URL consistent | |
166 args_hash = tuple(new["path_args"]) | 182 args_hash = tuple(new["path_args"]) |
167 LiberviaPage.pages_redirects.setdefault(new_data["page"], {})[ | 183 self.pages_redirects.setdefault(new_data["page"], {})[ |
168 args_hash | 184 args_hash |
169 ] = old | 185 ] = old |
170 | 186 |
171 # we need lists in query_args because it will be used | 187 # we need lists in query_args because it will be used |
172 # as it in request.path_args | 188 # as it in request.path_args |
211 name=new[u"page"] | 227 name=new[u"page"] |
212 ) | 228 ) |
213 ) | 229 ) |
214 else: | 230 else: |
215 if new[u"type"] == u"page": | 231 if new[u"type"] == u"page": |
216 page = LiberviaPage.getPageByName(new[u"page"]) | 232 page = self.getPageByName(new[u"page"]) |
217 url = page.getURL(*new.get(u"path_args", [])) | 233 url = page.getURL(*new.get(u"path_args", [])) |
218 self.inv_redirections[url] = old | 234 self.inv_redirections[url] = old |
219 continue | 235 continue |
220 | 236 |
221 # at this point we have a redirection URL in new, we can parse it | 237 # at this point we have a redirection URL in new, we can parse it |
222 new_url = urlparse.urlsplit(new.encode("utf-8")) | 238 new_url = urlparse.urlsplit(new.encode("utf-8")) |
223 | 239 |
224 # we handle the known URL schemes | 240 # we handle the known URL schemes |
225 if new_url.scheme == "xmpp": | 241 if new_url.scheme == "xmpp": |
226 location = LiberviaPage.getPagePathFromURI(new) | 242 location = self.getPagePathFromURI(new) |
227 if location is None: | 243 if location is None: |
228 log.warning( | 244 log.warning( |
229 _( | 245 _(u"ignoring redirection, no page found to handle this URI: " |
230 u"ignoring redirection, no page found to handle this URI: {uri}" | 246 u"{uri}").format(uri=new)) |
231 ).format(uri=new) | |
232 ) | |
233 continue | 247 continue |
234 request_data = self._getRequestData(location) | 248 request_data = self._getRequestData(location) |
235 if old: | 249 if old: |
236 self.inv_redirections[location] = old | 250 self.inv_redirections[location] = old |
237 | 251 |
238 elif new_url.scheme in ("", "http", "https"): | 252 elif new_url.scheme in ("", "http", "https"): |
239 # direct redirection | 253 # direct redirection |
240 if new_url.netloc: | 254 if new_url.netloc: |
241 raise NotImplementedError( | 255 raise NotImplementedError( |
242 u"netloc ({netloc}) is not implemented yet for url_redirections_dict, it is not possible to redirect to an external website".format( | 256 u"netloc ({netloc}) is not implemented yet for " |
243 netloc=new_url.netloc | 257 u"url_redirections_dict, it is not possible to redirect to an " |
244 ) | 258 u"external website".format(netloc=new_url.netloc)) |
245 ) | |
246 location = urlparse.urlunsplit( | 259 location = urlparse.urlunsplit( |
247 ("", "", new_url.path, new_url.query, new_url.fragment) | 260 ("", "", new_url.path, new_url.query, new_url.fragment) |
248 ).decode("utf-8") | 261 ).decode("utf-8") |
249 request_data = self._getRequestData(location) | 262 request_data = self._getRequestData(location) |
250 if old: | 263 if old: |
252 | 265 |
253 elif new_url.scheme in ("file"): | 266 elif new_url.scheme in ("file"): |
254 # file or directory | 267 # file or directory |
255 if new_url.netloc: | 268 if new_url.netloc: |
256 raise NotImplementedError( | 269 raise NotImplementedError( |
257 u"netloc ({netloc}) is not implemented for url redirection to file system, it is not possible to redirect to an external host".format( | 270 u"netloc ({netloc}) is not implemented for url redirection to " |
258 netloc=new_url.netloc | 271 u"file system, it is not possible to redirect to an external " |
259 ) | 272 "host".format( |
260 ) | 273 netloc=new_url.netloc)) |
261 path = urllib.unquote(new_url.path) | 274 path = urllib.unquote(new_url.path) |
262 if not os.path.isabs(path): | 275 if not os.path.isabs(path): |
263 raise ValueError( | 276 raise ValueError( |
264 u"file redirection must have an absolute path: e.g. file:/path/to/my/file" | 277 u"file redirection must have an absolute path: e.g. " |
265 ) | 278 u"file:/path/to/my/file") |
266 # for file redirection, we directly put child here | 279 # for file redirection, we directly put child here |
267 segments, dummy, last_segment = old.rpartition("/") | 280 segments, __, last_segment = old.rpartition("/") |
268 url_segments = segments.split("/") if segments else [] | 281 url_segments = segments.split("/") if segments else [] |
269 current = self | 282 current = self |
270 for segment in url_segments: | 283 for segment in url_segments: |
271 resource = web_resource.NoResource() | 284 resource = web_resource.NoResource() |
272 current.putChild(segment, resource) | 285 current.putChild(segment, resource) |
273 current = resource | 286 current = resource |
274 resource_class = ( | 287 resource_class = ( |
275 ProtectedFile if new_data.get("protected", True) else static.File | 288 ProtectedFile if new_data.get("protected", True) else static.File |
276 ) | 289 ) |
277 current.putChild(last_segment, resource_class(path)) | 290 current.putChild(last_segment, resource_class(path)) |
278 log.info( | 291 log.info(u"[{host_name}] Added redirection from /{old} to file system " |
279 u"Added redirection from /{old} to file system path {path}".format( | 292 u"path {path}".format(host_name=self.host_name, |
280 old=old.decode("utf-8"), path=path.decode("utf-8") | 293 old=old.decode("utf-8"), |
281 ) | 294 path=path.decode("utf-8"))) |
282 ) | |
283 continue # we don't want to use redirection system, so we continue here | 295 continue # we don't want to use redirection system, so we continue here |
284 | 296 |
285 else: | 297 else: |
286 raise NotImplementedError( | 298 raise NotImplementedError( |
287 u"{scheme}: scheme is not managed for url_redirections_dict".format( | 299 u"{scheme}: scheme is not managed for url_redirections_dict".format( |
289 ) | 301 ) |
290 ) | 302 ) |
291 | 303 |
292 self.redirections[old] = request_data | 304 self.redirections[old] = request_data |
293 if not old: | 305 if not old: |
294 log.info( | 306 log.info(_(u"[{host_name}] Root URL redirected to {uri}") |
295 _(u"Root URL redirected to {uri}").format( | 307 .format(host_name=self.host_name, |
296 uri=request_data[1].decode("utf-8") | 308 uri=request_data[1].decode("utf-8"))) |
297 ) | |
298 ) | |
299 | |
300 # no need to keep url_redirections*, they will not be used anymore | |
301 del options["url_redirections_dict"] | |
302 del options["url_redirections_profile"] | |
303 | 309 |
304 # the default root URL, if not redirected | 310 # the default root URL, if not redirected |
305 if not "" in self.redirections: | 311 if not "" in self.redirections: |
306 self.redirections[""] = self._getRequestData(C.LIBERVIA_MAIN_PAGE) | 312 self.redirections[""] = self._getRequestData(C.LIBERVIA_MAIN_PAGE) |
313 | |
314 def _setMenu(self, menus): | |
315 menus = menus.get(self.site_name, []) | |
316 main_menu = [] | |
317 for menu in menus: | |
318 if not menu: | |
319 msg = _(u"menu item can't be empty") | |
320 log.error(msg) | |
321 raise ValueError(msg) | |
322 elif isinstance(menu, list): | |
323 if len(menu) != 2: | |
324 msg = _( | |
325 u"menu item as list must be in the form [page_name, absolue URL]" | |
326 ) | |
327 log.error(msg) | |
328 raise ValueError(msg) | |
329 page_name, url = menu | |
330 else: | |
331 page_name = menu | |
332 try: | |
333 url = self.getPageByName(page_name).url | |
334 except KeyError as e: | |
335 log.error(_(u"Can'find a named page ({msg}), please check " | |
336 u"menu_json in configuration.").format(msg=e)) | |
337 raise e | |
338 main_menu.append((page_name, url)) | |
339 self.main_menu = main_menu | |
307 | 340 |
308 def _normalizeURL(self, url, lower=True): | 341 def _normalizeURL(self, url, lower=True): |
309 """Return URL normalized for self.redirections dict | 342 """Return URL normalized for self.redirections dict |
310 | 343 |
311 @param url(unicode): URL to normalize | 344 @param url(unicode): URL to normalize |
360 request._redirected | 393 request._redirected |
361 except AttributeError: | 394 except AttributeError: |
362 pass | 395 pass |
363 else: | 396 else: |
364 try: | 397 try: |
365 dummy, uri, dummy, dummy = request_data | 398 __, uri, __, __ = request_data |
366 except ValueError: | 399 except ValueError: |
367 uri = u"" | 400 uri = u"" |
368 log.error( | 401 log.error(D_( u"recursive redirection, please fix this URL:\n" |
369 D_( | 402 u"{old} ==> {new}").format( |
370 u"recursive redirection, please fix this URL:\n{old} ==> {new}" | 403 old=request.uri.decode("utf-8"), new=uri.decode("utf-8"))) |
371 ).format(old=request.uri.decode("utf-8"), new=uri.decode("utf-8")) | |
372 ) | |
373 return web_resource.NoResource() | 404 return web_resource.NoResource() |
374 | 405 |
375 request._redirected = True # here to avoid recursive redirections | 406 request._redirected = True # here to avoid recursive redirections |
376 | 407 |
377 if isinstance(request_data, dict): | 408 if isinstance(request_data, dict): |
378 if request_data["type"] == "page": | 409 if request_data["type"] == "page": |
379 try: | 410 try: |
380 page = LiberviaPage.getPageByName(request_data["page"]) | 411 page = self.getPageByName(request_data["page"]) |
381 except KeyError: | 412 except KeyError: |
382 log.error( | 413 log.error( |
383 _( | 414 _( |
384 u'Can\'t find page named "{name}" requested in redirection' | 415 u'Can\'t find page named "{name}" requested in redirection' |
385 ).format(name=request_data["page"]) | 416 ).format(name=request_data["page"]) |
411 request.args = args | 442 request.args = args |
412 | 443 |
413 # we start again to look for a child with the new url | 444 # we start again to look for a child with the new url |
414 return self.getChildWithDefault(path_list[0], request) | 445 return self.getChildWithDefault(path_list[0], request) |
415 | 446 |
447 def getPageByName(self, name): | |
448 """Retrieve page instance from its name | |
449 | |
450 @param name(unicode): name of the page | |
451 @return (LiberviaPage): page instance | |
452 @raise KeyError: the page doesn't exist | |
453 """ | |
454 return self.named_pages[name] | |
455 | |
456 def getPagePathFromURI(self, uri): | |
457 """Retrieve page URL from xmpp: URI | |
458 | |
459 @param uri(unicode): URI with a xmpp: scheme | |
460 @return (unicode,None): absolute path (starting from root "/") to page handling | |
461 the URI. | |
462 None is returned if no page has been registered for this URI | |
463 """ | |
464 uri_data = common_uri.parseXMPPUri(uri) | |
465 try: | |
466 page, cb = self.uri_callbacks[uri_data["type"], uri_data["sub_type"]] | |
467 except KeyError: | |
468 url = None | |
469 else: | |
470 url = cb(page, uri_data) | |
471 if url is None: | |
472 # no handler found | |
473 # we try to find a more generic one | |
474 try: | |
475 page, cb = self.uri_callbacks[uri_data["type"], None] | |
476 except KeyError: | |
477 pass | |
478 else: | |
479 url = cb(page, uri_data) | |
480 return url | |
481 | |
416 def getChildWithDefault(self, name, request): | 482 def getChildWithDefault(self, name, request): |
417 # XXX: this method is overriden only for root url | 483 # XXX: this method is overriden only for root url |
418 # which is the only ones who need to be handled before other children | 484 # which is the only ones who need to be handled before other children |
419 if name == "" and not request.postpath: | 485 if name == "" and not request.postpath: |
420 return self._redirect(request, self.redirections[""]) | 486 return self._redirect(request, self.redirections[""]) |
434 request.postpath = path_elt[idx:] | 500 request.postpath = path_elt[idx:] |
435 return self._redirect(request, request_data) | 501 return self._redirect(request, request_data) |
436 | 502 |
437 return resource | 503 return resource |
438 | 504 |
505 def putChild(self, path, resource): | |
506 """Add a child to the root resource""" | |
507 if not isinstance(resource, web_resource.EncodingResourceWrapper): | |
508 # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders) | |
509 resource = web_resource.EncodingResourceWrapper( | |
510 resource, [server.GzipEncoderFactory()]) | |
511 | |
512 super(LiberviaRootResource, self).putChild(path, resource) | |
513 | |
439 def createSimilarFile(self, path): | 514 def createSimilarFile(self, path): |
440 # XXX: this method need to be overriden to avoid recreating a LiberviaRootResource | 515 # XXX: this method need to be overriden to avoid recreating a LiberviaRootResource |
441 | 516 |
442 f = LiberviaRootResource.__base__( | 517 f = LiberviaRootResource.__base__( |
443 path, self.defaultType, self.ignoredExts, self.registry | 518 path, self.defaultType, self.ignoredExts, self.registry |
563 # @param node (unicode): node to delete | 638 # @param node (unicode): node to delete |
564 # @param items (iterable): id of item to retract | 639 # @param items (iterable): id of item to retract |
565 # @param notify (bool): True if notification is required | 640 # @param notify (bool): True if notification is required |
566 # """ | 641 # """ |
567 # profile = session_iface.ISATSession(self.session).profile | 642 # profile = session_iface.ISATSession(self.session).profile |
568 # return self.asyncBridgeCall("psRetractItem", service, node, item, notify, profile) | 643 # return self.asyncBridgeCall("psRetractItem", service, node, item, notify, |
644 # profile) | |
569 | 645 |
570 # def jsonrpc_psRetractItems(self, service, node, items, notify): | 646 # def jsonrpc_psRetractItems(self, service, node, items, notify): |
571 # """Delete a whole node | 647 # """Delete a whole node |
572 | 648 |
573 # @param service (unicode): service jid | 649 # @param service (unicode): service jid |
574 # @param node (unicode): node to delete | 650 # @param node (unicode): node to delete |
575 # @param items (iterable): ids of items to retract | 651 # @param items (iterable): ids of items to retract |
576 # @param notify (bool): True if notification is required | 652 # @param notify (bool): True if notification is required |
577 # """ | 653 # """ |
578 # profile = session_iface.ISATSession(self.session).profile | 654 # profile = session_iface.ISATSession(self.session).profile |
579 # return self.asyncBridgeCall("psRetractItems", service, node, items, notify, profile) | 655 # return self.asyncBridgeCall("psRetractItems", service, node, items, notify, |
656 # profile) | |
580 | 657 |
581 ## microblogging ## | 658 ## microblogging ## |
582 | 659 |
583 def jsonrpc_mbSend(self, service, node, mb_data): | 660 def jsonrpc_mbSend(self, service, node, mb_data): |
584 """Send microblog data | 661 """Send microblog data |
604 def jsonrpc_mbGet(self, service_jid, node, max_items, item_ids, extra): | 681 def jsonrpc_mbGet(self, service_jid, node, max_items, item_ids, extra): |
605 """Get last microblogs from publisher_jid | 682 """Get last microblogs from publisher_jid |
606 | 683 |
607 @param service_jid (unicode): pubsub service, usually publisher jid | 684 @param service_jid (unicode): pubsub service, usually publisher jid |
608 @param node(unicode): mblogs node, or empty string to get the defaut one | 685 @param node(unicode): mblogs node, or empty string to get the defaut one |
609 @param max_items (int): maximum number of item to get or C.NO_LIMIT to get everything | 686 @param max_items (int): maximum number of item to get or C.NO_LIMIT to get |
687 everything | |
610 @param item_ids (list[unicode]): list of item IDs | 688 @param item_ids (list[unicode]): list of item IDs |
611 @param rsm (dict): TODO | 689 @param rsm (dict): TODO |
612 @return: a deferred couple with the list of items and metadatas. | 690 @return: a deferred couple with the list of items and metadatas. |
613 """ | 691 """ |
614 profile = session_iface.ISATSession(self.session).profile | 692 profile = session_iface.ISATSession(self.session).profile |
618 | 696 |
619 def jsonrpc_mbGetFromMany(self, publishers_type, publishers, max_items, extra): | 697 def jsonrpc_mbGetFromMany(self, publishers_type, publishers, max_items, extra): |
620 """Get many blog nodes at once | 698 """Get many blog nodes at once |
621 | 699 |
622 @param publishers_type (unicode): one of "ALL", "GROUP", "JID" | 700 @param publishers_type (unicode): one of "ALL", "GROUP", "JID" |
623 @param publishers (tuple(unicode)): tuple of publishers (empty list for all, list of groups or list of jids) | 701 @param publishers (tuple(unicode)): tuple of publishers (empty list for all, |
624 @param max_items (int): maximum number of item to get or C.NO_LIMIT to get everything | 702 list of groups or list of jids) |
703 @param max_items (int): maximum number of item to get or C.NO_LIMIT to get | |
704 everything | |
625 @param extra (dict): TODO | 705 @param extra (dict): TODO |
626 @return (str): RT Deferred session id | 706 @return (str): RT Deferred session id |
627 """ | 707 """ |
628 profile = session_iface.ISATSession(self.session).profile | 708 profile = session_iface.ISATSession(self.session).profile |
629 return self.sat_host.bridgeCall( | 709 return self.sat_host.bridgeCall( |
647 rsm_dict, | 727 rsm_dict, |
648 rsm_comments_dict, | 728 rsm_comments_dict, |
649 ): | 729 ): |
650 """Helper method to get the microblogs and their comments in one shot | 730 """Helper method to get the microblogs and their comments in one shot |
651 | 731 |
652 @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") | 732 @param publishers_type (str): type of the list of publishers (one of "GROUP" or |
653 @param publishers (list): list of publishers, according to publishers_type (list of groups or list of jids) | 733 "JID" or "ALL") |
734 @param publishers (list): list of publishers, according to publishers_type | |
735 (list of groups or list of jids) | |
654 @param max_items (int): optional limit on the number of retrieved items. | 736 @param max_items (int): optional limit on the number of retrieved items. |
655 @param max_comments (int): maximum number of comments to retrieve | 737 @param max_comments (int): maximum number of comments to retrieve |
656 @param rsm_dict (dict): RSM data for initial items only | 738 @param rsm_dict (dict): RSM data for initial items only |
657 @param rsm_comments_dict (dict): RSM data for comments only | 739 @param rsm_comments_dict (dict): RSM data for comments only |
658 @param profile_key: profile key | 740 @param profile_key: profile key |
694 | 776 |
695 # if type_ in ("PUBLIC", "GROUP") and text: | 777 # if type_ in ("PUBLIC", "GROUP") and text: |
696 # if type_ == "PUBLIC": | 778 # if type_ == "PUBLIC": |
697 # #This text if for the public microblog | 779 # #This text if for the public microblog |
698 # log.debug("sending public blog") | 780 # log.debug("sending public blog") |
699 # return self.sat_host.bridge.sendGroupBlog("PUBLIC", (), text, extra, profile) | 781 # return self.sat_host.bridge.sendGroupBlog("PUBLIC", (), text, extra, |
782 # profile) | |
700 # else: | 783 # else: |
701 # log.debug("sending group blog") | 784 # log.debug("sending group blog") |
702 # dest = dest if isinstance(dest, list) else [dest] | 785 # dest = dest if isinstance(dest, list) else [dest] |
703 # return self.sat_host.bridge.sendGroupBlog("GROUP", dest, text, extra, profile) | 786 # return self.sat_host.bridge.sendGroupBlog("GROUP", dest, text, extra, |
787 # profile) | |
704 # else: | 788 # else: |
705 # raise Exception("Invalid data") | 789 # raise Exception("Invalid data") |
706 | 790 |
707 # def jsonrpc_deleteMblog(self, pub_data, comments): | 791 # def jsonrpc_deleteMblog(self, pub_data, comments): |
708 # """Delete a microblog node | 792 # """Delete a microblog node |
709 # @param pub_data: a tuple (service, comment node identifier, item identifier) | 793 # @param pub_data: a tuple (service, comment node identifier, item identifier) |
710 # @param comments: comments node identifier (for main item) or False | 794 # @param comments: comments node identifier (for main item) or False |
711 # """ | 795 # """ |
712 # profile = session_iface.ISATSession(self.session).profile | 796 # profile = session_iface.ISATSession(self.session).profile |
713 # return self.sat_host.bridge.deleteGroupBlog(pub_data, comments if comments else '', profile) | 797 # return self.sat_host.bridge.deleteGroupBlog(pub_data, comments if comments |
798 # else '', profile) | |
714 | 799 |
715 # def jsonrpc_updateMblog(self, pub_data, comments, message, extra={}): | 800 # def jsonrpc_updateMblog(self, pub_data, comments, message, extra={}): |
716 # """Modify a microblog node | 801 # """Modify a microblog node |
717 # @param pub_data: a tuple (service, comment node identifier, item identifier) | 802 # @param pub_data: a tuple (service, comment node identifier, item identifier) |
718 # @param comments: comments node identifier (for main item) or False | 803 # @param comments: comments node identifier (for main item) or False |
719 # @param message: new message | 804 # @param message: new message |
720 # @param extra: dict which option name as key, which can be: | 805 # @param extra: dict which option name as key, which can be: |
721 # - allow_comments: True to accept an other level of comments, False else (default: False) | 806 # - allow_comments: True to accept an other level of comments, False else |
807 # (default: False) | |
722 # - rich: if present, contain rich text in currently selected syntax | 808 # - rich: if present, contain rich text in currently selected syntax |
723 # """ | 809 # """ |
724 # profile = session_iface.ISATSession(self.session).profile | 810 # profile = session_iface.ISATSession(self.session).profile |
725 # if comments: | 811 # if comments: |
726 # extra['allow_comments'] = 'True' | 812 # extra['allow_comments'] = 'True' |
727 # return self.sat_host.bridge.updateGroupBlog(pub_data, comments if comments else '', message, extra, profile) | 813 # return self.sat_host.bridge.updateGroupBlog(pub_data, comments if comments |
814 # else '', message, extra, profile) | |
728 | 815 |
729 # def jsonrpc_sendMblogComment(self, node, text, extra={}): | 816 # def jsonrpc_sendMblogComment(self, node, text, extra={}): |
730 # """ Send microblog message | 817 # """ Send microblog message |
731 # @param node: url of the comments node | 818 # @param node: url of the comments node |
732 # @param text: comment | 819 # @param text: comment |
798 if ( | 885 if ( |
799 jid.JID(from_jid).userhost() != sat_jid.userhost() | 886 jid.JID(from_jid).userhost() != sat_jid.userhost() |
800 and jid.JID(to_jid).userhost() != sat_jid.userhost() | 887 and jid.JID(to_jid).userhost() != sat_jid.userhost() |
801 ): | 888 ): |
802 log.error( | 889 log.error( |
803 u"Trying to get history from a different jid (given (browser): {}, real (backend): {}), maybe a hack attempt ?".format( | 890 u"Trying to get history from a different jid (given (browser): {}, real " |
804 from_jid, sat_jid | 891 u"(backend): {}), maybe a hack attempt ?".format( from_jid, sat_jid)) |
805 ) | |
806 ) | |
807 return {} | 892 return {} |
808 d = self.asyncBridgeCall( | 893 d = self.asyncBridgeCall( |
809 "historyGet", from_jid, to_jid, size, between, search, profile | 894 "historyGet", from_jid, to_jid, size, between, search, profile) |
810 ) | |
811 | 895 |
812 def show(result_dbus): | 896 def show(result_dbus): |
813 result = [] | 897 result = [] |
814 for line in result_dbus: | 898 for line in result_dbus: |
815 # XXX: we have to do this stupid thing because Python D-Bus use its own types instead of standard types | 899 # XXX: we have to do this stupid thing because Python D-Bus use its own |
816 # and txJsonRPC doesn't accept D-Bus types, resulting in a empty query | 900 # types instead of standard types and txJsonRPC doesn't accept |
901 # D-Bus types, resulting in a empty query | |
817 uuid, timestamp, from_jid, to_jid, message, subject, mess_type, extra = ( | 902 uuid, timestamp, from_jid, to_jid, message, subject, mess_type, extra = ( |
818 line | 903 line |
819 ) | 904 ) |
820 result.append( | 905 result.append( |
821 ( | 906 ( |
985 | 1070 |
986 def jsonrpc_asyncGetParamA(self, param, category, attribute="value"): | 1071 def jsonrpc_asyncGetParamA(self, param, category, attribute="value"): |
987 """Return the parameter value for profile""" | 1072 """Return the parameter value for profile""" |
988 profile = session_iface.ISATSession(self.session).profile | 1073 profile = session_iface.ISATSession(self.session).profile |
989 if category == "Connection": | 1074 if category == "Connection": |
990 # we need to manage the followings params here, else SECURITY_LIMIT would block them | 1075 # we need to manage the followings params here, else SECURITY_LIMIT would |
1076 # block them | |
991 if param == "JabberID": | 1077 if param == "JabberID": |
992 return self.asyncBridgeCall( | 1078 return self.asyncBridgeCall( |
993 "asyncGetParamA", param, category, attribute, profile_key=profile | 1079 "asyncGetParamA", param, category, attribute, profile_key=profile |
994 ) | 1080 ) |
995 elif param == "autoconnect": | 1081 elif param == "autoconnect": |
1009 return self.sat_host.bridgeCall( | 1095 return self.sat_host.bridgeCall( |
1010 "setParam", name, value, category, C.SECURITY_LIMIT, profile | 1096 "setParam", name, value, category, C.SECURITY_LIMIT, profile |
1011 ) | 1097 ) |
1012 | 1098 |
1013 def jsonrpc_launchAction(self, callback_id, data): | 1099 def jsonrpc_launchAction(self, callback_id, data): |
1014 # FIXME: any action can be launched, this can be a huge security issue if callback_id can be guessed | 1100 # FIXME: any action can be launched, this can be a huge security issue if |
1015 # a security system with authorised callback_id must be implemented, similar to the one for authorised params | 1101 # callback_id can be guessed a security system with authorised |
1102 # callback_id must be implemented, similar to the one for authorised params | |
1016 profile = session_iface.ISATSession(self.session).profile | 1103 profile = session_iface.ISATSession(self.session).profile |
1017 d = self.asyncBridgeCall("launchAction", callback_id, data, profile) | 1104 d = self.asyncBridgeCall("launchAction", callback_id, data, profile) |
1018 return d | 1105 return d |
1019 | 1106 |
1020 def jsonrpc_chatStateComposing(self, to_jid_s): | 1107 def jsonrpc_chatStateComposing(self, to_jid_s): |
1066 def setRequest(self, request, profile, register_with_ext_jid=False): | 1153 def setRequest(self, request, profile, register_with_ext_jid=False): |
1067 """Add the given profile to the waiting list. | 1154 """Add the given profile to the waiting list. |
1068 | 1155 |
1069 @param request (server.Request): the connection request | 1156 @param request (server.Request): the connection request |
1070 @param profile (str): %(doc_profile)s | 1157 @param profile (str): %(doc_profile)s |
1071 @param register_with_ext_jid (bool): True if we will try to register the profile with an external XMPP account credentials | 1158 @param register_with_ext_jid (bool): True if we will try to register the |
1159 profile with an external XMPP account credentials | |
1072 """ | 1160 """ |
1073 dc = reactor.callLater(BRIDGE_TIMEOUT, self.purgeRequest, profile) | 1161 dc = reactor.callLater(BRIDGE_TIMEOUT, self.purgeRequest, profile) |
1074 self[profile] = (request, dc, register_with_ext_jid) | 1162 self[profile] = (request, dc, register_with_ext_jid) |
1075 | 1163 |
1076 def purgeRequest(self, profile): | 1164 def purgeRequest(self, profile): |
1115 def render(self, request): | 1203 def render(self, request): |
1116 """ | 1204 """ |
1117 Render method with some hacks: | 1205 Render method with some hacks: |
1118 - if login is requested, try to login with form data | 1206 - if login is requested, try to login with form data |
1119 - except login, every method is jsonrpc | 1207 - except login, every method is jsonrpc |
1120 - user doesn't need to be authentified for explicitely listed methods, but must be for all others | 1208 - user doesn't need to be authentified for explicitely listed methods, |
1209 but must be for all others | |
1121 """ | 1210 """ |
1122 if request.postpath == ["login"]: | 1211 if request.postpath == ["login"]: |
1123 return self.loginOrRegister(request) | 1212 return self.loginOrRegister(request) |
1124 _session = request.getSession() | 1213 _session = request.getSession() |
1125 parsed = jsonrpclib.loads(request.content.read()) | 1214 parsed = jsonrpclib.loads(request.content.read()) |
1177 def _loginAccount(self, request): | 1266 def _loginAccount(self, request): |
1178 """Try to authenticate the user with the request information. | 1267 """Try to authenticate the user with the request information. |
1179 | 1268 |
1180 will write to request a constant indicating the state: | 1269 will write to request a constant indicating the state: |
1181 - C.PROFILE_LOGGED: profile is connected | 1270 - C.PROFILE_LOGGED: profile is connected |
1182 - C.PROFILE_LOGGED_EXT_JID: profile is connected and an external jid has been used | 1271 - C.PROFILE_LOGGED_EXT_JID: profile is connected and an external jid has |
1272 been used | |
1183 - C.SESSION_ACTIVE: session was already active | 1273 - C.SESSION_ACTIVE: session was already active |
1184 - C.BAD_REQUEST: something is wrong in the request (bad arguments) | 1274 - C.BAD_REQUEST: something is wrong in the request (bad arguments) |
1185 - C.PROFILE_AUTH_ERROR: either the profile (login) or the profile password is wrong | 1275 - C.PROFILE_AUTH_ERROR: either the profile (login) or the profile password |
1186 - C.XMPP_AUTH_ERROR: the profile is authenticated but the XMPP password is wrong | 1276 is wrong |
1187 - C.ALREADY_WAITING: a request has already been submitted for this profil, C.PROFILE_LOGGED_EXT_JID)e | 1277 - C.XMPP_AUTH_ERROR: the profile is authenticated but the XMPP password |
1278 is wrong | |
1279 - C.ALREADY_WAITING: a request has already been submitted for this profile, | |
1280 C.PROFILE_LOGGED_EXT_JID) | |
1188 - C.NOT_CONNECTED: connection has not been established | 1281 - C.NOT_CONNECTED: connection has not been established |
1189 the request will then be finished | 1282 the request will then be finished |
1190 @param request: request of the register form | 1283 @param request: request of the register form |
1191 """ | 1284 """ |
1192 try: | 1285 try: |
1256 def jsonrpc_getSessionMetadata(self): | 1349 def jsonrpc_getSessionMetadata(self): |
1257 """Return metadata useful on session start | 1350 """Return metadata useful on session start |
1258 | 1351 |
1259 @return (dict): metadata which can have the following keys: | 1352 @return (dict): metadata which can have the following keys: |
1260 "plugged" (bool): True if a profile is already plugged | 1353 "plugged" (bool): True if a profile is already plugged |
1261 "warning" (unicode): a security warning message if plugged is False and if it make sense | 1354 "warning" (unicode): a security warning message if plugged is False and if |
1262 this key may not be present | 1355 it make sense. |
1356 This key may not be present. | |
1263 "allow_registration" (bool): True if registration is allowed | 1357 "allow_registration" (bool): True if registration is allowed |
1264 this key is only present if profile is unplugged | 1358 this key is only present if profile is unplugged |
1265 @return: a couple (registered, message) with: | 1359 @return: a couple (registered, message) with: |
1266 - registered: | 1360 - registered: |
1267 - message: | 1361 - message: |
1347 if self.queue[profile]: | 1441 if self.queue[profile]: |
1348 return self.queue[profile].pop(0) | 1442 return self.queue[profile].pop(0) |
1349 else: | 1443 else: |
1350 # the queue is empty, we delete the profile from queue | 1444 # the queue is empty, we delete the profile from queue |
1351 del self.queue[profile] | 1445 del self.queue[profile] |
1352 _session.lock() # we don't want the session to expire as long as this connection is active | 1446 _session.lock() # we don't want the session to expire as long as this |
1447 # connection is active | |
1353 | 1448 |
1354 def unlock(signal, profile): | 1449 def unlock(signal, profile): |
1355 _session.unlock() | 1450 _session.unlock() |
1356 try: | 1451 try: |
1357 source_defer = self.signalDeferred[profile] | 1452 source_defer = self.signalDeferred[profile] |
1390 return genericCb | 1485 return genericCb |
1391 | 1486 |
1392 def actionNewHandler(self, action_data, action_id, security_limit, profile): | 1487 def actionNewHandler(self, action_data, action_id, security_limit, profile): |
1393 """actionNew handler | 1488 """actionNew handler |
1394 | 1489 |
1395 XXX: We need need a dedicated handler has actionNew use a security_limit which must be managed | 1490 XXX: We need need a dedicated handler has actionNew use a security_limit |
1491 which must be managed | |
1396 @param action_data(dict): see bridge documentation | 1492 @param action_data(dict): see bridge documentation |
1397 @param action_id(unicode): identitifer of the action | 1493 @param action_id(unicode): identitifer of the action |
1398 @param security_limit(int): %(doc_security_limit)s | 1494 @param security_limit(int): %(doc_security_limit)s |
1399 @param profile(unicode): %(doc_profile)s | 1495 @param profile(unicode): %(doc_profile)s |
1400 """ | 1496 """ |
1421 | 1517 |
1422 def connected(self, profile, jid_s): | 1518 def connected(self, profile, jid_s): |
1423 """Connection is done. | 1519 """Connection is done. |
1424 | 1520 |
1425 @param profile (unicode): %(doc_profile)s | 1521 @param profile (unicode): %(doc_profile)s |
1426 @param jid_s (unicode): the JID that we were assigned by the server, as the resource might differ from the JID we asked for. | 1522 @param jid_s (unicode): the JID that we were assigned by the server, as the |
1523 resource might differ from the JID we asked for. | |
1427 """ | 1524 """ |
1428 # FIXME: _logged should not be called from here, check this code | 1525 # FIXME: _logged should not be called from here, check this code |
1429 # FIXME: check if needed to connect with external jid | 1526 # FIXME: check if needed to connect with external jid |
1430 # jid_s is handled in QuickApp.connectionHandler already | 1527 # jid_s is handled in QuickApp.connectionHandler already |
1431 # assert self.register # register must be plugged | 1528 # assert self.register # register must be plugged |
1440 # and if we have 2 disconnection in a short time, we don't try to reconnect | 1537 # and if we have 2 disconnection in a short time, we don't try to reconnect |
1441 # and display an error message | 1538 # and display an error message |
1442 disconnect_delta = time.time() - self._last_service_prof_disconnect | 1539 disconnect_delta = time.time() - self._last_service_prof_disconnect |
1443 if disconnect_delta < 15: | 1540 if disconnect_delta < 15: |
1444 log.error( | 1541 log.error( |
1445 _( | 1542 _(u"Service profile disconnected twice in a short time, please " |
1446 u"Service profile disconnected twice in a short time, please check connection" | 1543 u"check connection")) |
1447 ) | |
1448 ) | |
1449 else: | 1544 else: |
1450 log.info( | 1545 log.info( |
1451 _( | 1546 _(u"Service profile has been disconnected, but we need it! " |
1452 u"Service profile has been disconnected, but we need it! Reconnecting it..." | 1547 u"Reconnecting it...")) |
1453 ) | |
1454 ) | |
1455 d = self.sat_host.bridgeCall( | 1548 d = self.sat_host.bridgeCall( |
1456 "connect", profile, self.sat_host.options["passphrase"], {} | 1549 "connect", profile, self.sat_host.options["passphrase"], {} |
1457 ) | 1550 ) |
1458 d.addErrback( | 1551 d.addErrback( |
1459 lambda failure_: log.error( | 1552 lambda failure_: log.error(_( |
1460 _( | 1553 u"Can't reconnect service profile, please check connection: " |
1461 u"Can't reconnect service profile, please check connection: {reason}" | 1554 u"{reason}").format(reason=failure_))) |
1462 ).format(reason=failure_) | |
1463 ) | |
1464 ) | |
1465 self._last_service_prof_disconnect = time.time() | 1555 self._last_service_prof_disconnect = time.time() |
1466 return | 1556 return |
1467 | 1557 |
1468 if not profile in self.sat_host.prof_connected: | 1558 if not profile in self.sat_host.prof_connected: |
1469 log.info( | 1559 log.info(_(u"'disconnected' signal received for a not connected profile " |
1470 _( | 1560 u"({profile})").format(profile=profile)) |
1471 u"'disconnected' signal received for a not connected profile ({profile})" | |
1472 ).format(profile=profile) | |
1473 ) | |
1474 return | 1561 return |
1475 self.sat_host.prof_connected.remove(profile) | 1562 self.sat_host.prof_connected.remove(profile) |
1476 if profile in self.signalDeferred: | 1563 if profile in self.signalDeferred: |
1477 self.signalDeferred[profile].callback(("disconnected",)) | 1564 self.signalDeferred[profile].callback(("disconnected",)) |
1478 del self.signalDeferred[profile] | 1565 del self.signalDeferred[profile] |
1531 def render(self, request): | 1618 def render(self, request): |
1532 """ | 1619 """ |
1533 Render method with some hacks: | 1620 Render method with some hacks: |
1534 - if login is requested, try to login with form data | 1621 - if login is requested, try to login with form data |
1535 - except login, every method is jsonrpc | 1622 - except login, every method is jsonrpc |
1536 - user doesn't need to be authentified for getSessionMetadata, but must be for all other methods | 1623 - user doesn't need to be authentified for getSessionMetadata, but must be |
1624 for all other methods | |
1537 """ | 1625 """ |
1538 filename = self._getFileName(request) | 1626 filename = self._getFileName(request) |
1539 filepath = os.path.join(self.upload_dir, filename) | 1627 filepath = os.path.join(self.upload_dir, filename) |
1540 # FIXME: the uploaded file is fully loaded in memory at form parsing time so far | 1628 # FIXME: the uploaded file is fully loaded in memory at form parsing time so far |
1541 # (see twisted.web.http.Request.requestReceived). A custom requestReceived should | 1629 # (see twisted.web.http.Request.requestReceived). A custom requestReceived |
1542 # be written in the futur. In addition, it is not yet possible to get progression informations | 1630 # should be written in the futur. In addition, it is not yet possible to |
1543 # (see http://twistedmatrix.com/trac/ticket/288) | 1631 # get progression informations (see |
1632 # http://twistedmatrix.com/trac/ticket/288) | |
1544 | 1633 |
1545 with open(filepath, "w") as f: | 1634 with open(filepath, "w") as f: |
1546 f.write(request.args[self.NAME][0]) | 1635 f.write(request.args[self.NAME][0]) |
1547 | 1636 |
1548 def finish(d): | 1637 def finish(d): |
1566 def _getFileName(self, request): | 1655 def _getFileName(self, request): |
1567 extension = os.path.splitext(request.args["filename"][0])[1] | 1656 extension = os.path.splitext(request.args["filename"][0])[1] |
1568 return "%s%s" % ( | 1657 return "%s%s" % ( |
1569 str(uuid.uuid4()), | 1658 str(uuid.uuid4()), |
1570 extension, | 1659 extension, |
1571 ) # XXX: chromium doesn't seem to play song without the .ogg extension, even with audio/ogg mime-type | 1660 ) # XXX: chromium doesn't seem to play song without the .ogg extension, even |
1661 # with audio/ogg mime-type | |
1572 | 1662 |
1573 def _fileWritten(self, request, filepath): | 1663 def _fileWritten(self, request, filepath): |
1574 """Called once the file is actually written on disk | 1664 """Called once the file is actually written on disk |
1575 @param request: HTTP request object | 1665 @param request: HTTP request object |
1576 @param filepath: full filepath on the server | 1666 @param filepath: full filepath on the server |
1636 | 1726 |
1637 ## bridge ## | 1727 ## bridge ## |
1638 self.bridge = Bridge() | 1728 self.bridge = Bridge() |
1639 self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb) | 1729 self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb) |
1640 | 1730 |
1731 @property | |
1732 def roots(self): | |
1733 """Return available virtual host roots | |
1734 | |
1735 Root resources are only returned once, even if they are present for multiple | |
1736 named vhosts. Order is not relevant, except for default vhost which is always | |
1737 returned first. | |
1738 @return (list[web_resource.Resource]): all vhost root resources | |
1739 """ | |
1740 roots = list(set(self.vhost_root.hosts.values())) | |
1741 default = self.vhost_root.default | |
1742 if default is not None and default not in roots: | |
1743 roots.insert(0, default) | |
1744 return roots | |
1745 | |
1641 def _namespacesGetCb(self, ns_map): | 1746 def _namespacesGetCb(self, ns_map): |
1642 self.ns_map = ns_map | 1747 self.ns_map = ns_map |
1643 | 1748 |
1644 def _namespacesGetEb(self, failure_): | 1749 def _namespacesGetEb(self, failure_): |
1645 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) | 1750 log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_)) |
1647 def _front_url_filter(self, ctx, relative_url): | 1752 def _front_url_filter(self, ctx, relative_url): |
1648 template_data = ctx[u'template_data'] | 1753 template_data = ctx[u'template_data'] |
1649 return os.path.join(u'/', C.TPL_RESOURCE, template_data.site or u'sat', | 1754 return os.path.join(u'/', C.TPL_RESOURCE, template_data.site or u'sat', |
1650 C.TEMPLATE_TPL_DIR, template_data.theme, relative_url) | 1755 C.TEMPLATE_TPL_DIR, template_data.theme, relative_url) |
1651 | 1756 |
1652 def backendReady(self, dummy): | 1757 def _moveFirstLevelToDict(self, options, key): |
1653 self.root = root = LiberviaRootResource(self.html_dir) | 1758 """Read a config option and put value at first level into u'' dict |
1759 | |
1760 This is useful to put values for Libervia official site directly in dictionary, | |
1761 and to use site_name as keys when external sites are used. | |
1762 options will be modified in place | |
1763 """ | |
1764 try: | |
1765 conf = options[key] | |
1766 except KeyError: | |
1767 return | |
1768 if not isinstance(conf, dict): | |
1769 options[key] = {u'': conf} | |
1770 return | |
1771 default_dict = conf.setdefault(u'', {}) | |
1772 to_delete = [] | |
1773 for key, value in conf.iteritems(): | |
1774 if not isinstance(value, dict): | |
1775 default_dict[key] = value | |
1776 to_delete.append(key) | |
1777 for key in to_delete: | |
1778 del conf[key] | |
1779 | |
1780 def backendReady(self, __): | |
1781 self.media_dir = self.bridge.getConfig("", "media_dir") | |
1782 self.local_dir = self.bridge.getConfig("", "local_dir") | |
1783 self.cache_root_dir = os.path.join(self.local_dir, C.CACHE_DIR) | |
1784 | |
1785 self._moveFirstLevelToDict(self.options, "url_redirections_dict") | |
1786 self._moveFirstLevelToDict(self.options, "menu_json") | |
1787 | |
1788 # we create virtual hosts and import Libervia pages into them | |
1789 self.renderer = template.Renderer(self, self._front_url_filter) | |
1790 self.vhost_root = vhost.NameVirtualHost() | |
1791 default_site_path = os.path.dirname(libervia.__file__) | |
1792 # self.sat_root is official Libervia site | |
1793 self.sat_root = default_root = LiberviaRootResource( | |
1794 host=self, host_name=u'', site_name=u'', site_path=default_site_path, | |
1795 path=self.html_dir) | |
1796 LiberviaPage.importPages(self, self.sat_root) | |
1797 # FIXME: handle _setMenu in a more generic way, taking care of external sites | |
1798 self.sat_root._setMenu(self.options["menu_json"]) | |
1799 self.vhost_root.default = default_root | |
1800 existing_vhosts = {u'': default_root} | |
1801 | |
1802 for host_name, site_name in self.options["vhosts_dict"].iteritems(): | |
1803 try: | |
1804 site_path = self.renderer.sites_paths[site_name] | |
1805 except KeyError: | |
1806 log.warning(_( | |
1807 u"host {host_name} link to non existing site {site_name}, ignoring " | |
1808 u"it").format(host_name=host_name, site_name=site_name)) | |
1809 continue | |
1810 if site_name in existing_vhosts: | |
1811 # we have an alias host, we re-use existing resource | |
1812 res = existing_vhosts[site_name] | |
1813 else: | |
1814 # for root path we first check if there is a global static dir | |
1815 # if not, we use default template's static dic | |
1816 root_path = os.path.join(site_path, C.TEMPLATE_STATIC_DIR) | |
1817 if not os.path.isdir(root_path): | |
1818 root_path = os.path.join( | |
1819 site_path, C.TEMPLATE_TPL_DIR, C.TEMPLATE_THEME_DEFAULT, | |
1820 C.TEMPLATE_STATIC_DIR) | |
1821 res = LiberviaRootResource( | |
1822 host=self, | |
1823 host_name=host_name, | |
1824 site_name=site_name, | |
1825 site_path=site_path, | |
1826 path=root_path) | |
1827 self.vhost_root.addHost(host_name.encode('utf-8'), res) | |
1828 LiberviaPage.importPages(self, res) | |
1829 # FIXME: default pages are accessible if not overriden by external website | |
1830 # while necessary for login or re-using existing pages | |
1831 # we may want to disable access to the page by direct URL | |
1832 # (e.g. /blog disabled except if called by external site) | |
1833 LiberviaPage.importPages(self, res, root_path=default_site_path) | |
1834 res._setMenu(self.options["menu_json"]) | |
1835 | |
1836 templates_res = web_resource.Resource() | |
1837 self.putChildAll(C.TPL_RESOURCE, templates_res) | |
1838 for site_name, site_path in self.renderer.sites_paths.iteritems(): | |
1839 templates_res.putChild(site_name or u'sat', ProtectedFile(site_path)) | |
1840 | |
1654 _register = Register(self) | 1841 _register = Register(self) |
1655 _upload_radiocol = UploadManagerRadioCol(self) | 1842 _upload_radiocol = UploadManagerRadioCol(self) |
1656 _upload_avatar = UploadManagerAvatar(self) | 1843 _upload_avatar = UploadManagerAvatar(self) |
1657 d = self.bridgeCall("namespacesGet") | 1844 d = self.bridgeCall("namespacesGet") |
1658 d.addCallback(self._namespacesGetCb) | 1845 d.addCallback(self._namespacesGetCb) |
1671 "paramUpdate", | 1858 "paramUpdate", |
1672 ]: | 1859 ]: |
1673 self.bridge.register_signal( | 1860 self.bridge.register_signal( |
1674 signal_name, self.signal_handler.getGenericCb(signal_name) | 1861 signal_name, self.signal_handler.getGenericCb(signal_name) |
1675 ) | 1862 ) |
1676 # XXX: actionNew is handled separately because the handler must manage security_limit | 1863 # XXX: actionNew is handled separately because the handler must manage |
1864 # security_limit | |
1677 self.bridge.register_signal("actionNew", self.signal_handler.actionNewHandler) | 1865 self.bridge.register_signal("actionNew", self.signal_handler.actionNewHandler) |
1678 # plugins | 1866 # plugins |
1679 for signal_name in [ | 1867 for signal_name in [ |
1680 "psEvent", | 1868 "psEvent", |
1681 "mucRoomJoined", | 1869 "mucRoomJoined", |
1700 "chatStateReceived", | 1888 "chatStateReceived", |
1701 ]: | 1889 ]: |
1702 self.bridge.register_signal( | 1890 self.bridge.register_signal( |
1703 signal_name, self.signal_handler.getGenericCb(signal_name), "plugin" | 1891 signal_name, self.signal_handler.getGenericCb(signal_name), "plugin" |
1704 ) | 1892 ) |
1705 self.media_dir = self.bridge.getConfig("", "media_dir") | |
1706 self.local_dir = self.bridge.getConfig("", "local_dir") | |
1707 self.cache_root_dir = os.path.join(self.local_dir, C.CACHE_DIR) | |
1708 | 1893 |
1709 # JSON APIs | 1894 # JSON APIs |
1710 self.putChild("json_signal_api", self.signal_handler) | 1895 self.putChildSAT("json_signal_api", self.signal_handler) |
1711 self.putChild("json_api", MethodHandler(self)) | 1896 self.putChildSAT("json_api", MethodHandler(self)) |
1712 self.putChild("register_api", _register) | 1897 self.putChildSAT("register_api", _register) |
1713 | 1898 |
1714 # files upload | 1899 # files upload |
1715 self.putChild("upload_radiocol", _upload_radiocol) | 1900 self.putChildSAT("upload_radiocol", _upload_radiocol) |
1716 self.putChild("upload_avatar", _upload_avatar) | 1901 self.putChildSAT("upload_avatar", _upload_avatar) |
1717 | 1902 |
1718 # static pages | 1903 # static pages |
1719 self.putChild("blog_legacy", MicroBlog(self)) | 1904 self.putChildSAT("blog_legacy", MicroBlog(self)) |
1720 self.putChild(C.THEMES_URL, ProtectedFile(self.themes_dir)) | 1905 self.putChildSAT(C.THEMES_URL, ProtectedFile(self.themes_dir)) |
1721 | 1906 |
1722 # websocket | 1907 # websocket |
1723 if self.options["connection_type"] in ("https", "both"): | 1908 if self.options["connection_type"] in ("https", "both"): |
1724 wss = websockets.LiberviaPageWSProtocol.getResource(self, secure=True) | 1909 wss = websockets.LiberviaPageWSProtocol.getResource(self, secure=True) |
1725 self.putChild("wss", wss) | 1910 self.putChildAll("wss", wss) |
1726 if self.options["connection_type"] in ("http", "both"): | 1911 if self.options["connection_type"] in ("http", "both"): |
1727 ws = websockets.LiberviaPageWSProtocol.getResource(self, secure=False) | 1912 ws = websockets.LiberviaPageWSProtocol.getResource(self, secure=False) |
1728 self.putChild("ws", ws) | 1913 self.putChildAll("ws", ws) |
1729 | 1914 |
1730 # Libervia pages | |
1731 LiberviaPage.importPages(self) | |
1732 LiberviaPage.setMenu(self.options["menu_json"]) | |
1733 ## following signal is needed for cache handling in Libervia pages | 1915 ## following signal is needed for cache handling in Libervia pages |
1734 self.bridge.register_signal( | 1916 self.bridge.register_signal( |
1735 "psEventRaw", partial(LiberviaPage.onNodeEvent, self), "plugin" | 1917 "psEventRaw", partial(LiberviaPage.onNodeEvent, self), "plugin" |
1736 ) | 1918 ) |
1737 self.bridge.register_signal( | 1919 self.bridge.register_signal( |
1749 "progressError", partial(ProgressHandler._signal, "error") | 1931 "progressError", partial(ProgressHandler._signal, "error") |
1750 ) | 1932 ) |
1751 | 1933 |
1752 # media dirs | 1934 # media dirs |
1753 # FIXME: get rid of dirname and "/" in C.XXX_DIR | 1935 # FIXME: get rid of dirname and "/" in C.XXX_DIR |
1754 self.putChild(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir)) | 1936 self.putChildAll(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir)) |
1755 self.cache_resource = web_resource.NoResource() | 1937 self.cache_resource = web_resource.NoResource() |
1756 self.putChild(C.CACHE_DIR, self.cache_resource) | 1938 self.putChildAll(C.CACHE_DIR, self.cache_resource) |
1757 | 1939 |
1758 # special | 1940 # special |
1759 self.putChild( | 1941 self.putChildSAT( |
1760 "radiocol", | 1942 "radiocol", |
1761 ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"), | 1943 ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"), |
1762 ) # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir | 1944 ) # FIXME: We cheat for PoC because we know we are on the same host, so we use |
1945 # directly upload dir | |
1763 # pyjamas tests, redirected only for dev versions | 1946 # pyjamas tests, redirected only for dev versions |
1764 if self.version[-1] == "D": | 1947 if self.version[-1] == "D": |
1765 self.putChild("test", web_util.Redirect("/libervia_test.html")) | 1948 self.putChildSAT("test", web_util.Redirect("/libervia_test.html")) |
1766 | 1949 |
1767 # redirections | 1950 # redirections |
1768 root._initRedirections(self.options) | 1951 for root in self.roots: |
1952 root._initRedirections(self.options) | |
1953 | |
1954 # no need to keep url_redirections_dict, it will not be used anymore | |
1955 del self.options["url_redirections_dict"] | |
1769 | 1956 |
1770 server.Request.defaultContentType = "text/html; charset=utf-8" | 1957 server.Request.defaultContentType = "text/html; charset=utf-8" |
1771 wrapped = web_resource.EncodingResourceWrapper( | 1958 wrapped = web_resource.EncodingResourceWrapper( |
1772 root, [server.GzipEncoderFactory()] | 1959 self.vhost_root, [server.GzipEncoderFactory()] |
1773 ) | 1960 ) |
1774 self.site = server.Site(wrapped) | 1961 self.site = server.Site(wrapped) |
1775 self.site.sessionFactory = LiberviaSession | 1962 self.site.sessionFactory = LiberviaSession |
1776 self.renderer = template.Renderer(self, self._front_url_filter) | |
1777 templates_res = web_resource.Resource() | |
1778 self.putChild(C.TPL_RESOURCE, templates_res) | |
1779 for site_name, site_path in self.renderer.sites_paths.iteritems(): | |
1780 templates_res.putChild(site_name or u'sat', ProtectedFile(site_path)) | |
1781 | 1963 |
1782 def initEb(self, failure): | 1964 def initEb(self, failure): |
1783 log.error(_(u"Init error: {msg}").format(msg=failure)) | 1965 log.error(_(u"Init error: {msg}").format(msg=failure)) |
1784 reactor.stop() | 1966 reactor.stop() |
1785 return failure | 1967 return failure |
1804 """Return the short version of Libervia""" | 1986 """Return the short version of Libervia""" |
1805 return C.APP_VERSION | 1987 return C.APP_VERSION |
1806 | 1988 |
1807 @property | 1989 @property |
1808 def full_version(self): | 1990 def full_version(self): |
1809 """Return the full version of Libervia (with extra data when in development mode)""" | 1991 """Return the full version of Libervia (with extra data when in dev mode)""" |
1810 version = self.version | 1992 version = self.version |
1811 if version[-1] == "D": | 1993 if version[-1] == "D": |
1812 # we are in debug version, we add extra data | 1994 # we are in debug version, we add extra data |
1813 try: | 1995 try: |
1814 return self._version_cache | 1996 return self._version_cache |
1871 sat_session.profile = profile | 2053 sat_session.profile = profile |
1872 self.prof_connected.add(profile) | 2054 self.prof_connected.add(profile) |
1873 cache_dir = os.path.join( | 2055 cache_dir = os.path.join( |
1874 self.cache_root_dir, u"profiles", regex.pathEscape(profile) | 2056 self.cache_root_dir, u"profiles", regex.pathEscape(profile) |
1875 ) | 2057 ) |
1876 # FIXME: would be better to have a global /cache URL which redirect to profile's cache directory, without uuid | 2058 # FIXME: would be better to have a global /cache URL which redirect to |
2059 # profile's cache directory, without uuid | |
1877 self.cache_resource.putChild(sat_session.uuid, ProtectedFile(cache_dir)) | 2060 self.cache_resource.putChild(sat_session.uuid, ProtectedFile(cache_dir)) |
1878 log.debug( | 2061 log.debug( |
1879 _(u"profile cache resource added from {uuid} to {path}").format( | 2062 _(u"profile cache resource added from {uuid} to {path}").format( |
1880 uuid=sat_session.uuid, path=cache_dir | 2063 uuid=sat_session.uuid, path=cache_dir |
1881 ) | 2064 ) |
1914 @param login(unicode): user login | 2097 @param login(unicode): user login |
1915 can be profile name | 2098 can be profile name |
1916 can be profile@[libervia_domain.ext] | 2099 can be profile@[libervia_domain.ext] |
1917 can be a jid (a new profile will be created with this jid if needed) | 2100 can be a jid (a new profile will be created with this jid if needed) |
1918 @param password(unicode): user password | 2101 @param password(unicode): user password |
1919 @return (unicode, None): C.SESSION_ACTIVE: if session was aleady active else self._logged value | 2102 @return (unicode, None): C.SESSION_ACTIVE: if session was aleady active else |
2103 self._logged value | |
1920 @raise exceptions.DataError: invalid login | 2104 @raise exceptions.DataError: invalid login |
1921 @raise exceptions.ProfileUnknownError: this login doesn't exist | 2105 @raise exceptions.ProfileUnknownError: this login doesn't exist |
1922 @raise exceptions.PermissionError: a login is not accepted (e.g. empty password not allowed) | 2106 @raise exceptions.PermissionError: a login is not accepted (e.g. empty password |
2107 not allowed) | |
1923 @raise exceptions.NotReady: a profile connection is already waiting | 2108 @raise exceptions.NotReady: a profile connection is already waiting |
1924 @raise exceptions.TimeoutError: didn't received and answer from Bridge | 2109 @raise exceptions.TimeoutError: didn't received and answer from Bridge |
1925 @raise exceptions.InternalError: unknown error | 2110 @raise exceptions.InternalError: unknown error |
1926 @raise ValueError(C.PROFILE_AUTH_ERROR): invalid login and/or password | 2111 @raise ValueError(C.PROFILE_AUTH_ERROR): invalid login and/or password |
1927 @raise ValueError(C.XMPP_AUTH_ERROR): invalid XMPP account password | 2112 @raise ValueError(C.XMPP_AUTH_ERROR): invalid XMPP account password |
1960 if ( | 2145 if ( |
1961 login_jid is not None and login_jid.user | 2146 login_jid is not None and login_jid.user |
1962 ): # try to create a new sat profile using the XMPP credentials | 2147 ): # try to create a new sat profile using the XMPP credentials |
1963 if not self.options["allow_registration"]: | 2148 if not self.options["allow_registration"]: |
1964 log.warning( | 2149 log.warning( |
1965 u"Trying to register JID account while registration is not allowed" | 2150 u"Trying to register JID account while registration is not " |
1966 ) | 2151 u"allowed") |
1967 raise failure.Failure( | 2152 raise failure.Failure( |
1968 exceptions.DataError( | 2153 exceptions.DataError( |
1969 u"JID login while registration is not allowed" | 2154 u"JID login while registration is not allowed" |
1970 ) | 2155 ) |
1971 ) | 2156 ) |
1990 sat_session = session_iface.ISATSession(request.getSession()) | 2175 sat_session = session_iface.ISATSession(request.getSession()) |
1991 if sat_session.profile: | 2176 if sat_session.profile: |
1992 # yes, there is | 2177 # yes, there is |
1993 if sat_session.profile != profile: | 2178 if sat_session.profile != profile: |
1994 # it's a different profile, we need to disconnect it | 2179 # it's a different profile, we need to disconnect it |
1995 log.warning( | 2180 log.warning(_( |
1996 _( | 2181 u"{new_profile} requested login, but {old_profile} was already " |
1997 u"{new_profile} requested login, but {old_profile} was already connected, disconnecting {old_profile}" | 2182 u"connected, disconnecting {old_profile}").format( |
1998 ).format(old_profile=sat_session.profile, new_profile=profile) | 2183 old_profile=sat_session.profile, new_profile=profile)) |
1999 ) | |
2000 self.purgeSession(request) | 2184 self.purgeSession(request) |
2001 | 2185 |
2002 if self.waiting_profiles.getRequest(profile): | 2186 if self.waiting_profiles.getRequest(profile): |
2003 # FIXME: check if and when this can happen | 2187 # FIXME: check if and when this can happen |
2004 raise failure.Failure(exceptions.NotReady("Already waiting")) | 2188 raise failure.Failure(exceptions.NotReady("Already waiting")) |
2008 connected = yield self.bridgeCall(connect_method, profile, password) | 2192 connected = yield self.bridgeCall(connect_method, profile, password) |
2009 except Exception as failure_: | 2193 except Exception as failure_: |
2010 fault = failure_.faultString | 2194 fault = failure_.faultString |
2011 self.waiting_profiles.purgeRequest(profile) | 2195 self.waiting_profiles.purgeRequest(profile) |
2012 if fault in ("PasswordError", "ProfileUnknownError"): | 2196 if fault in ("PasswordError", "ProfileUnknownError"): |
2013 log.info( | 2197 log.info(u"Profile {profile} doesn't exist or the submitted password is " |
2014 u"Profile {profile} doesn't exist or the submitted password is wrong".format( | 2198 u"wrong".format( profile=profile)) |
2015 profile=profile | |
2016 ) | |
2017 ) | |
2018 raise failure.Failure(ValueError(C.PROFILE_AUTH_ERROR)) | 2199 raise failure.Failure(ValueError(C.PROFILE_AUTH_ERROR)) |
2019 elif fault == "SASLAuthError": | 2200 elif fault == "SASLAuthError": |
2020 log.info( | 2201 log.info(u"The XMPP password of profile {profile} is wrong" |
2021 u"The XMPP password of profile {profile} is wrong".format( | 2202 .format(profile=profile)) |
2022 profile=profile | |
2023 ) | |
2024 ) | |
2025 raise failure.Failure(ValueError(C.XMPP_AUTH_ERROR)) | 2203 raise failure.Failure(ValueError(C.XMPP_AUTH_ERROR)) |
2026 elif fault == "NoReply": | 2204 elif fault == "NoReply": |
2027 log.info( | 2205 log.info(_(u"Did not receive a reply (the timeout expired or the " |
2028 _( | 2206 u"connection is broken)")) |
2029 "Did not receive a reply (the timeout expired or the connection is broken)" | |
2030 ) | |
2031 ) | |
2032 raise exceptions.TimeOutError | 2207 raise exceptions.TimeOutError |
2033 else: | 2208 else: |
2034 log.error( | 2209 log.error(u'Unmanaged fault string "{fault}" in errback for the ' |
2035 u'Unmanaged fault string "{fault}" in errback for the connection of profile {profile}'.format( | 2210 u'connection of profile {profile}'.format( |
2036 fault=fault, profile=profile | 2211 fault=fault, profile=profile)) |
2037 ) | |
2038 ) | |
2039 raise failure.Failure(exceptions.InternalError(fault)) | 2212 raise failure.Failure(exceptions.InternalError(fault)) |
2040 | 2213 |
2041 if connected: | 2214 if connected: |
2042 # profile is already connected in backend | 2215 # profile is already connected in backend |
2043 # do we have a corresponding session in Libervia? | 2216 # do we have a corresponding session in Libervia? |
2045 if sat_session.profile: | 2218 if sat_session.profile: |
2046 # yes, session is active | 2219 # yes, session is active |
2047 if sat_session.profile != profile: | 2220 if sat_session.profile != profile: |
2048 # existing session should have been ended above | 2221 # existing session should have been ended above |
2049 # so this line should never be reached | 2222 # so this line should never be reached |
2050 log.error( | 2223 log.error(_( |
2051 _( | 2224 u"session profile [{session_profile}] differs from login " |
2052 u"session profile [{session_profile}] differs from login profile [{profile}], this should not happen!" | 2225 u"profile [{profile}], this should not happen!") |
2053 ).format(session_profile=sat_session.profile, profile=profile) | 2226 .format(session_profile=sat_session.profile, profile=profile)) |
2054 ) | |
2055 raise exceptions.InternalError("profile mismatch") | 2227 raise exceptions.InternalError("profile mismatch") |
2056 defer.returnValue(C.SESSION_ACTIVE) | 2228 defer.returnValue(C.SESSION_ACTIVE) |
2057 log.info( | 2229 log.info( |
2058 _( | 2230 _( |
2059 u"profile {profile} was already connected in backend".format( | 2231 u"profile {profile} was already connected in backend".format( |
2131 | 2303 |
2132 def eb(e): | 2304 def eb(e): |
2133 log.error(_(u"Connection failed: %s") % e) | 2305 log.error(_(u"Connection failed: %s") % e) |
2134 self.stop() | 2306 self.stop() |
2135 | 2307 |
2136 def initOk(dummy): | 2308 def initOk(__): |
2137 try: | 2309 try: |
2138 connected = self.bridge.isConnected(C.SERVICE_PROFILE) | 2310 connected = self.bridge.isConnected(C.SERVICE_PROFILE) |
2139 except Exception as e: | 2311 except Exception as e: |
2140 # we don't want the traceback | 2312 # we don't want the traceback |
2141 msg = [l for l in unicode(e).split("\n") if l][-1] | 2313 msg = [l for l in unicode(e).split("\n") if l][-1] |
2142 log.error( | 2314 log.error( |
2143 u"Can't check service profile ({profile}), are you sure it exists ?\n{error}".format( | 2315 u"Can't check service profile ({profile}), are you sure it exists ?" |
2144 profile=C.SERVICE_PROFILE, error=msg | 2316 u"\n{error}".format(profile=C.SERVICE_PROFILE, error=msg)) |
2145 ) | |
2146 ) | |
2147 self.stop() | 2317 self.stop() |
2148 return | 2318 return |
2149 if not connected: | 2319 if not connected: |
2150 self.bridge.connect( | 2320 self.bridge.connect( |
2151 C.SERVICE_PROFILE, | 2321 C.SERVICE_PROFILE, |
2159 | 2329 |
2160 self.initialised.addCallback(initOk) | 2330 self.initialised.addCallback(initOk) |
2161 | 2331 |
2162 ## URLs ## | 2332 ## URLs ## |
2163 | 2333 |
2164 def putChild(self, path, resource): | 2334 def putChildSAT(self, path, resource): |
2165 """Add a child to the root resource""" | 2335 """Add a child to the sat resource""" |
2336 self.sat_root.putChild(path, resource) | |
2337 | |
2338 def putChildAll(self, path, resource): | |
2339 """Add a child to all vhost root resources""" | |
2340 # we wrap before calling putChild, to avoid having useless multiple instances | |
2341 # of the resource | |
2166 # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders) | 2342 # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders) |
2167 self.root.putChild( | 2343 wrapped_res = web_resource.EncodingResourceWrapper( |
2168 path, | 2344 resource, [server.GzipEncoderFactory()]) |
2169 web_resource.EncodingResourceWrapper(resource, [server.GzipEncoderFactory()]), | 2345 for root in self.roots: |
2170 ) | 2346 root.putChild(path, wrapped_res) |
2171 | 2347 |
2172 def getExtBaseURLData(self, request): | 2348 def getExtBaseURLData(self, request): |
2173 """Retrieve external base URL Data | 2349 """Retrieve external base URL Data |
2174 | 2350 |
2175 this method tried to retrieve the base URL found by external user | 2351 this method tried to retrieve the base URL found by external user |
2176 It does by checking in this order: | 2352 It does by checking in this order: |
2177 - base_url_ext option from configuration | 2353 - base_url_ext option from configuration |
2178 - proxy x-forwarder-host headers | 2354 - proxy x-forwarder-host headers |
2179 - URL of the request | 2355 - URL of the request |
2180 @return (urlparse.SplitResult): SplitResult instance with only scheme and netloc filled | 2356 @return (urlparse.SplitResult): SplitResult instance with only scheme and |
2357 netloc filled | |
2181 """ | 2358 """ |
2182 ext_data = self.base_url_ext_data | 2359 ext_data = self.base_url_ext_data |
2183 url_path = request.URLPath() | 2360 url_path = request.URLPath() |
2184 if not ext_data.scheme or not ext_data.netloc: | 2361 if not ext_data.scheme or not ext_data.netloc: |
2185 # ext_data is not specified, we check headers | 2362 # ext_data is not specified, we check headers |
2243 query, | 2420 query, |
2244 fragment, | 2421 fragment, |
2245 ) | 2422 ) |
2246 ) | 2423 ) |
2247 | 2424 |
2248 def checkRedirection(self, url): | 2425 def checkRedirection(self, vhost_root, url): |
2249 """check is a part of the URL prefix is redirected then replace it | 2426 """check is a part of the URL prefix is redirected then replace it |
2250 | 2427 |
2428 @param vhost_root(web_resource.Resource): root of this virtual host | |
2251 @param url(unicode): url to check | 2429 @param url(unicode): url to check |
2252 @return (unicode): possibly redirected URL which should link to the same location | 2430 @return (unicode): possibly redirected URL which should link to the same location |
2253 """ | 2431 """ |
2254 inv_redirections = self.root.inv_redirections | 2432 inv_redirections = vhost_root.inv_redirections |
2255 url_parts = url.strip(u"/").split(u"/") | 2433 url_parts = url.strip(u"/").split(u"/") |
2256 for idx in xrange(len(url), 0, -1): | 2434 for idx in xrange(len(url), 0, -1): |
2257 test_url = u"/" + u"/".join(url_parts[:idx]) | 2435 test_url = u"/" + u"/".join(url_parts[:idx]) |
2258 if test_url in inv_redirections: | 2436 if test_url in inv_redirections: |
2259 rem_url = url_parts[idx:] | 2437 rem_url = url_parts[idx:] |
2432 ) | 2610 ) |
2433 ) | 2611 ) |
2434 self.quit(2) | 2612 self.quit(2) |
2435 except OpenSSL.crypto.Error: | 2613 except OpenSSL.crypto.Error: |
2436 log.error( | 2614 log.error( |
2437 u"Error while parsing file {path} for option {option}, are you sure it is a valid .pem file?".format( | 2615 u"Error while parsing file {path} for option {option}, are you sure " |
2438 path=path, option=option | 2616 u"it is a valid .pem file?".format( path=path, option=option)) |
2439 ) | |
2440 ) | |
2441 if ( | 2617 if ( |
2442 option == "tls_private_key" | 2618 option == "tls_private_key" |
2443 and self.options["tls_certificate"] == path | 2619 and self.options["tls_certificate"] == path |
2444 ): | 2620 ): |
2445 log.error( | 2621 log.error( |
2446 u"You are using the same file for private key and public certificate, make sure that both a in {path} or use --tls_private_key option".format( | 2622 u"You are using the same file for private key and public " |
2447 path=path | 2623 u"certificate, make sure that both a in {path} or use " |
2448 ) | 2624 u"--tls_private_key option".format(path=path)) |
2449 ) | |
2450 self.quit(2) | 2625 self.quit(2) |
2451 | 2626 |
2452 return ssl.CertificateOptions(**cert_options) | 2627 return ssl.CertificateOptions(**cert_options) |
2453 | 2628 |
2454 ## service management ## | 2629 ## service management ## |
2455 | 2630 |
2456 def _startService(self, dummy=None): | 2631 def _startService(self, __=None): |
2457 """Actually start the HTTP(S) server(s) after the profile for Libervia is connected. | 2632 """Actually start the HTTP(S) server(s) after the profile for Libervia is connected. |
2458 | 2633 |
2459 @raise ImportError: OpenSSL is not available | 2634 @raise ImportError: OpenSSL is not available |
2460 @raise IOError: the certificate file doesn't exist | 2635 @raise IOError: the certificate file doesn't exist |
2461 @raise OpenSSL.crypto.Error: the certificate file is invalid | 2636 @raise OpenSSL.crypto.Error: the certificate file is invalid |