Package gavo :: Package web :: Module ifpages
[frames] | no frames]

Source Code for Module gavo.web.ifpages

  1  """ 
  2  Infrastructure pages. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import datetime 
 12  import io 
 13  import os 
 14  import time 
 15   
 16  import pkg_resources 
 17  from nevow import inevow 
 18  from nevow import rend 
 19  from nevow import static 
 20  from nevow import url 
 21  from twisted.internet import reactor 
 22  from twisted.web import http 
 23   
 24  from gavo import base 
 25  from gavo import registry 
 26  from gavo import svcs 
 27  from gavo import utils 
 28  from gavo.base import meta 
 29  from gavo.votable import V 
 30  from gavo.web import caching 
 31  from gavo.web import grend 
 32   
 33  # prefer globally installed version to what we deliver 
 34  try: 
 35          import rjsmin 
 36  except ImportError: 
 37          from gavo.imp import rjsmin 
 38   
39 -class LoginPage(rend.Page, grend.GavoRenderMixin):
40 """a page that logs people in or out. 41 42 You should usually give a nextURL parameter in the context, the page 43 the user is returned to afte login. 44 45 If the user is already authenticated, this will do a logout (by 46 sending a 403). 47 """
48 - def __init__(self, ctx):
49 rend.Page.__init__(self) 50 self.request = inevow.IRequest(ctx) 51 self.nextURL = self.request.args.get("nextURL", ["/"])[0]
52
53 - def render_nextURL(self, ctx, data):
54 return ctx.tag(href=self.nextURL)
55
56 - def render_iflogged(self, ctx, data):
57 if self.request.getUser(): 58 return ctx.tag 59 return ""
60
61 - def render_ifnotlogged(self, ctx, data):
62 if not self.request.getUser(): 63 return ctx.tag 64 return ""
65
66 - def data_loggedUser(self, ctx, data):
67 return self.request.getUser()
68
69 - def renderHTTP(self, ctx):
70 relogging = base.parseBooleanLiteral(utils.getfirst( 71 self.request.args, "relog", default="False")) 72 if self.request.getUser(): # user is logged in... 73 if relogging: # ...and wants to log out: show login dialog... 74 raise svcs.Authenticate() 75 else: # ...and has just logged in: forward to destination 76 return url.URL.fromContext(ctx).click(self.nextURL) 77 else: # user is not logged in 78 if relogging: #...but was and has just logged out: forward to dest 79 return url.URL.fromContext(ctx).click(self.nextURL) 80 else: # ... and wants to log in. 81 raise svcs.Authenticate()
82 83 docFactory = svcs.loadSystemTemplate("loginout.html")
84 85
86 -def _replaceConfigStrings(srcPath, registry):
87 src = open(srcPath).read().decode("utf-8") 88 src = src.replace("__site_path__", base.getConfig("web", "nevowRoot")) 89 src = src.replace("__site_url__", base.makeAbsoluteURL("/")) 90 return src.encode("utf-8")
91 92
93 -class TemplatedPage(grend.CustomTemplateMixin, grend.ServiceBasedPage):
94 """a "server-wide" template. 95 96 For now, they all are based on the dc root service. 97 """ 98 checkedRenderer = False
99 - def __init__(self, ctx, fName):
100 self.customTemplate = fName 101 grend.ServiceBasedPage.__init__(self, ctx, 102 base.caches.getRD(registry.SERVICELIST_ID 103 ).getById("root")) 104 self.metaCarrier = meta.MetaMixin() 105 self.metaCarrier.setMetaParent(self.service) 106 self.metaCarrier.setMeta("dateUpdated", 107 utils.formatISODT( 108 datetime.datetime.fromtimestamp(os.path.getmtime(fName))))
109 110
111 -def minifyJS(ctx, path):
112 """returns javascript in path minified. 113 114 You can turn off auto-minification by setting [web] jsSource to True; 115 that's sometimes convenient while debugging the javascript. 116 117 If jsSource is false (the default), changes to javascript are only picked 118 up on a server reload. 119 """ 120 with open(path) as f: 121 if base.getConfig("web", "jsSource"): 122 return f.read() 123 else: 124 return rjsmin.jsmin(f.read())
125 126
127 -def expandTemplate(ctx, fName):
128 """renders fName as a template on the root service. 129 """ 130 return TemplatedPage(ctx, fName)
131 132
133 -class StaticFile(rend.Page):
134 """a file from the file system, served pretty directly. 135 136 Some of these static files can be javascript (that's usually minified 137 on the fly) or nevow templates. These we want to cache. For everything 138 else, we don't win anything by caching, but, when we serve large data, 139 we can DoS ourselves. So, we decide manually whether to cache. 140 141 The caches are bound to an RD passed in as cacheRD at construction time. 142 For system resources, that should be getRD(registry.SERVICELIST_ID). 143 144 On-the-fly processing is based on certain magic mime types: 145 text/nevow-template is rendered and application/javascript is minified. 146 """ 147 defaultType = "application/octet-stream" 148 149 magicMimes = { 150 "text/nevow-template": expandTemplate, 151 "application/javascript": minifyJS, 152 } 153
154 - def __init__(self, fName, cacheRD):
155 self.fName, self.cacheRD = fName, cacheRD
156
157 - def getMimeType(self):
158 ext = os.path.splitext(self.fName)[-1] 159 return static.File.contentTypes.get(ext, self.defaultType)
160
161 - def renderPlain(self, request):
162 static.FileTransfer( 163 open(self.fName), os.path.getsize(self.fName), request)
164
165 - def renderHTTP(self, ctx):
166 request = inevow.IRequest(ctx) 167 modStamp = max(self.cacheRD.loadedAt, os.path.getmtime(self.fName)) 168 if request.setLastModified(modStamp) is http.CACHED: 169 return '' 170 171 if not os.path.isfile(self.fName): 172 raise svcs.ForbiddenURI("Only plain files are served here") 173 174 mime = self.getMimeType() 175 if mime in self.magicMimes: 176 cache = base.caches.getPageCache(self.cacheRD.sourceId) 177 cachedRes = cache.get(self.fName) 178 if cachedRes is not None and cachedRes.creationStamp>modStamp: 179 return cachedRes 180 caching.instrumentRequestForCaching(request, 181 caching.enterIntoCacheAs(self.fName, cache)) 182 return (self.magicMimes[mime](ctx, self.fName)) 183 184 else: 185 request.setHeader("content-type", mime) 186 self.renderPlain(request) 187 188 return (request.deferred)
189 190 191 ########### Begin Debian Fallbacks: If no local jquery and friends are 192 # present (as happens when the official package is used) 193 # several javascript libraries are taken from where Debian packages 194 # put them. The URI path parts DaCHS sees them under are mapped to 195 # the absolute path names or resource instances in DEBIAN_FALLBACKS. 196
197 -class GavoJquery(rend.Page):
198 """synthesises a query-gavo.js from Debian packages. 199 """ 200 INSTALL_ROOT = "/usr/share/javascript/" 201 PARTS = ["jquery/jquery.js", 202 io.BytesIO(""" 203 (function( factory ) { 204 if ( typeof define === "function" && define.amd ) { 205 206 // AMD. Register as an anonymous module. 207 define([ "jquery" ], factory ); 208 } else { 209 210 // Browser globals 211 factory( jQuery ); 212 } 213 }(function( $ ) { 214 215 $.ui = $.ui || {}; 216 217 var version = $.ui.version = "1.12.1"; 218 """), 219 "jquery-ui/ui/widget.js", 220 "jquery-ui/ui/position.js", 221 "jquery-ui/ui/data.js", 222 "jquery-ui/ui/disable-selection.js", 223 "jquery-ui/ui/focusable.js", 224 "jquery-ui/ui/form-reset-mixin.js", 225 "jquery-ui/ui/jquery-1-7.js", 226 "jquery-ui/ui/keycode.js", 227 "jquery-ui/ui/labels.js", 228 "jquery-ui/ui/scroll-parent.js", 229 "jquery-ui/ui/tabbable.js", 230 "jquery-ui/ui/unique-id.js", 231 "jquery-ui/ui/widgets/mouse.js", 232 io.BytesIO(""" 233 var plugin = $.ui.plugin = { 234 add: function( module, option, set ) { 235 var i, 236 proto = $.ui[ module ].prototype; 237 for ( i in set ) { 238 proto.plugins[ i ] = proto.plugins[ i ] || []; 239 proto.plugins[ i ].push( [ option, set[ i ] ] ); 240 } 241 }, 242 call: function( instance, name, args, allowDisconnected ) { 243 var i, 244 set = instance.plugins[ name ]; 245 246 if ( !set ) { 247 return; 248 } 249 250 if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || 251 instance.element[ 0 ].parentNode.nodeType === 11 ) ) { 252 return; 253 } 254 255 for ( i = 0; i < set.length; i++ ) { 256 if ( instance.options[ set[ i ][ 0 ] ] ) { 257 set[ i ][ 1 ].apply( instance.element, args ); 258 } 259 } 260 } 261 }; 262 """), 263 "jquery-ui/ui/widgets/draggable.js", 264 "jquery-ui/ui/widgets/resizable.js", 265 266 io.BytesIO("}));"), 267 ] 268
269 - def __init__(self):
270 self.content = None 271 rend.Page.__init__(self)
272
273 - def _getContent(self):
274 parts = [] 275 for part in self.PARTS: 276 if hasattr(part, "read"): 277 parts.append(part.read()) 278 else: 279 with open(os.path.join(self.INSTALL_ROOT, part)) as f: 280 parts.append(f.read()) 281 stuff = "\n".join(parts) 282 283 self.modStamp = time.time() 284 if base.getConfig("web", "jsSource"): 285 return stuff 286 else: 287 return rjsmin.jsmin(stuff)
288
289 - def renderHTTP(self, ctx):
290 if self.content is None: 291 self.content = self._getContent() 292 293 request = inevow.IRequest(ctx) 294 if request.setLastModified(self.modStamp) is http.CACHED: 295 return '' 296 297 return self.content
298 299 300 DEBIAN_FALLBACKS = { 301 'js/jquery-gavo.js': GavoJquery(), 302 'js/jquery.flot.js': 303 "/usr/share/javascript/jquery-flot/jquery.flot.js", 304 } 305 306 ########### End Debian Fallbacks 307
308 -class StaticServer(rend.Page):
309 """is a server for various static files. 310 311 This is basically like static.File, except 312 313 - we don't do directory listings 314 - we don't bother with ranges 315 - we look for each file in a user area and then in the system area. 316 """
317 - def __init__(self):
318 rend.Page.__init__(self) 319 self.userPath = utils.ensureOneSlash( 320 os.path.abspath(os.path.join(base.getConfig("webDir"), "nv_static"))) 321 self.systemPath = utils.ensureOneSlash( 322 os.path.abspath(pkg_resources.resource_filename('gavo', "resources/web")))
323
324 - def renderHTTP(self, ctx):
325 raise svcs.UnknownURI("What did you expect here?")
326
327 - def locateChild(self, ctx, segments):
328 relPath = "/".join(segments) 329 associatedRD = base.caches.getRD(registry.SERVICELIST_ID) 330 331 path = os.path.abspath(self.userPath+relPath) 332 if os.path.exists(path): 333 if not path.startswith(self.userPath): 334 raise svcs.ForbiddenURI( 335 "%s is not located in the static user directory"%path) 336 return StaticFile(path, associatedRD), () 337 338 path = os.path.abspath(self.systemPath+relPath) 339 if os.path.exists(path): 340 if not path.startswith(self.systemPath): 341 raise svcs.ForbiddenURI( 342 "%s is not located in the static system directory"%path) 343 return StaticFile(path, associatedRD), () 344 345 if relPath in DEBIAN_FALLBACKS: 346 res = DEBIAN_FALLBACKS[relPath] 347 if isinstance(res, basestring): 348 return StaticFile(res, associatedRD), () 349 else: 350 return res, () 351 352 raise svcs.UnknownURI("No matching file," 353 " neither built-in nor user-provided")
354 355 processors = { 356 ".shtml": _replaceConfigStrings, 357 }
358 359
360 -class RobotsTxt(rend.Page):
361 """A page combining some built-in robots.txt material with etc/robots.txt 362 if it exists. 363 """ 364 builtin = utils.fixIndentation(""" 365 Disallow: /login 366 Disallow: /seffe 367 """, "") 368
369 - def _getContent(self):
370 content = self.builtin 371 try: 372 with open(os.path.join(base.getConfig("webDir"), "robots.txt")) as f: 373 content = content+"\n"+f.read() 374 except IOError: 375 pass 376 return content
377
378 - def renderHTTP(self, ctx):
379 request = inevow.IRequest(ctx) 380 request.setHeader("content-type", "text/plain") 381 return self._getContent()
382
383 - def locateChild(self, segments):
384 return None
385 386
387 -class ServiceUnavailable(rend.Page):
388 """A page to be rendered in emergencies. 389 390 Essentially, this is a 503 with a text taken from stateDir/MAINT. 391 392 Root checks for the presence of that file before returning this 393 page, so (ignoring race conditions) this page assumes it's there. 394 """
395 - def renderHTTP(self, ctx):
396 request = inevow.IRequest(ctx) 397 request.setResponseCode(503) 398 request.setHeader("retry-after", "3600") 399 400 with open(os.path.join(base.getConfig("stateDir"), "MAINT")) as f: 401 try: 402 maintText = f.read().decode("utf-8") 403 except IOError: 404 maintText = "Unspecified problem" 405 406 vot = V.VOTABLE[ 407 V.RESOURCE(type="results")[ 408 V.INFO(name="QUERY_STATUS", value="ERROR")[ 409 maintText] 410 ]] 411 412 request.setHeader("content-type", "text/xml") 413 return ("<?xml-stylesheet href='/static" 414 "/xsl/dalierror-to-html.xsl' type='text/xsl'?>" 415 +vot.render())
416 417
418 -class WellKnown(rend.Page):
419 """A handler for the .well-known hierarchy. 420 421 We only do something for ACME at this point. 422 """ 423 acmeChallengeDir = os.path.join(base.getConfig("statedir"), "acme-challenge") 424
425 - def locateChild(self, ctx, segments):
426 if segments[0]=="acme-challenge" and len(segments)==2: 427 if os.path.exists(self.acmeChallengeDir): 428 return static.File( 429 os.path.join(self.acmeChallengeDir, segments[1])), () 430 431 raise svcs.UnknownURI("Only ACME supported in .well-known")
432 433
434 -class CurReaders(rend.Page):
435 """A page returning an approximate number of clients served currently. 436 """
437 - def renderHTTP(self, ctx):
438 request = inevow.IRequest(ctx) 439 request.setHeader("content-type", "text/plain") 440 # TODO: the following must be amended for IPv6 441 hostsActive = len([1 442 for r in reactor.getReaders() 443 if hasattr(r, "getHost") and r.getHost().host!='0.0.0.0']) 444 return str(hostsActive)+"\n"
445