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