Source code for backend.handlers

import logging
import peewee
import tornado.auth
import tornado.web
import tornado.escape
import tornado.httpclient
import os.path
import datetime
import urllib.parse
from tornado.escape import json_encode

import db


[docs]class BaseHandler(tornado.web.RequestHandler): """ Base Handler. Handlers should not inherit from this class directly but from either SafeHandler or UnsafeHandler to make security status explicit. """
[docs] def prepare(self): ## Make sure we have the xsrf_token, this will generate the xsrf cookie if it isn't set self.xsrf_token #pylint: disable=pointless-statement if db.database.is_closed(): try: db.database.connect() except peewee.DatabaseError as e: logging.error("Failed to connect to database: {}".format(e))
[docs] def on_finish(self): if not db.database.is_closed(): db.database.close()
[docs] def get_current_user(self): email = self.get_secure_cookie('email') name = self.get_secure_cookie('user') identity = self.get_secure_cookie('identity') # Fix ridiculous bug with quotation marks showing on the web if name and (name[0] == '"') and (name[-1] == '"'): name = name[1:-1] if identity: try: return db.User.select().where( db.User.identity == identity ).get() except db.User.DoesNotExist: ## Not saved in the database yet try: return db.User(email = email.decode('utf-8'), name = name.decode('utf-8'), identity = identity.decode('utf-8')) except peewee.OperationalError as e: logging.error("Can't create new user: {}".format(e)) else: return None
[docs] def set_user_msg(self, msg, level="info"): """ This function sets the user message cookie. The system takes four default levels, 'success', 'info', 'warning', and 'error'. Messages set to other levels will be defaulted to 'info'. """ if level not in ["success", "info", "warning", "error"]: level = "info" self.set_cookie("msg", urllib.parse.quote( json_encode({"msg":msg, "level":level}) ) )
[docs] def write_error(self, status_code, **kwargs): """ Overwrites write_error method to have custom error pages. http://tornado.readthedocs.org/en/latest/web.html#tornado.web.RequestHandler.write_error """ logging.info("Error do something here again")
[docs] def write(self, chunk): if not isinstance(chunk, dict): super().write(chunk) return new_chunk = _convert_keys_to_hump_back(chunk) super().write(new_chunk)
def _convert_keys_to_hump_back(chunk): """ Converts keys given in snake_case to humpBack-case, while preserving the capitalization of the first letter. """ if isinstance(chunk, list): return [_convert_keys_to_hump_back(e) for e in chunk] if not isinstance(chunk, dict): return chunk new_chunk = {} for k, v in chunk.items(): # First character should be the same as in the original string new_key = k[0] + "".join([a[0].upper() + a[1:] for a in k.split("_")])[1:] new_chunk[new_key] = _convert_keys_to_hump_back(v) return new_chunk
[docs]class UnsafeHandler(BaseHandler): pass
[docs]class SafeHandler(BaseHandler): """ All handlers that need authentication and authorization should inherit from this class. """
[docs] def prepare(self): """ This method is called before any other method. Having the decorator @tornado.web.authenticated here implies that all the Handlers that inherit from this one are going to require authentication in all their methods. """ super().prepare() if not self.current_user: logging.debug("No current user: Send error 403") self.send_error(status_code=403)
[docs]class AuthorizedHandler(SafeHandler):
[docs] def prepare(self): logging.debug("Checking if user is authorized") super().prepare() if self._finished: return kwargs = self.path_kwargs if not 'dataset' in kwargs: logging.debug("No dataset: Send error 403") self.send_error(status_code=403) return ds_version = kwargs['ds_version'] if 'ds_version' in kwargs else None if not self.current_user.has_access(db.get_dataset(kwargs['dataset']), ds_version): logging.debug("No user access: Send error 403") self.send_error(status_code=403) return logging.debug("User is authorized")
[docs]class AdminHandler(SafeHandler):
[docs] def prepare(self): super().prepare() if self._finished: return kwargs = self.path_kwargs if not kwargs['dataset']: logging.debug("No dataset: Send error 403") self.send_error(status_code=403) return if not self.current_user.is_admin( db.get_dataset(kwargs['dataset']) ): logging.debug("No user admin: Send error 403") self.send_error(status_code=403) return
[docs]class SafeStaticFileHandler(tornado.web.StaticFileHandler, SafeHandler): """ Serve static files for logged in users """
[docs]class BaseStaticNginxFileHandler(UnsafeHandler): """ Serve static files for users from the nginx frontend Requires a ``path`` argument in constructor which should be the root of the nginx frontend where the files can be found. Then configure the nginx frontend something like this: :: location <path> { internal; alias <location of files>; } """
[docs] def initialize(self, path): if not path.startswith("/"): path = "/" + path self.root = path
[docs] def get(self, dataset, file, ds_version=None, user=None): logging.debug("Want to download dataset {} ({})".format(dataset, file)) if not user: user = self.current_user try: dbfile = (db.DatasetFile.select() .join(db.DatasetVersion) .where((db.DatasetFile.name == file) & (db.DatasetVersion.version == ds_version)) .get()) except db.DatasetFile.DoesNotExist: self.send_error(status_code=403) return db.UserDownloadLog.create(user = user, dataset_file = dbfile) abspath = os.path.abspath(os.path.join(self.root, file)) self.set_header("X-Accel-Redirect", abspath) self.set_header("Content-Disposition", "attachment") logging.debug("Setting X-Accel-Redirect to {}".format(abspath)) self.finish()
[docs]class AuthorizedStaticNginxFileHandler(AuthorizedHandler, BaseStaticNginxFileHandler): """ Serve static files for authenticated users from the nginx frontend Requires a "path" argument in constructor which should be the root of the nginx frontend where the files can be found. Then configure the nginx frontend something like this: :: location <path> { internal; alias <location of files>; } """
[docs]class TemporaryStaticNginxFileHandler(BaseStaticNginxFileHandler):
[docs] def get(self, dataset, ds_version, hash_value, file): logging.debug("Want to download hash {} ({})".format(hash_value, file)) linkhash = (db.Linkhash .select() .join(db.DatasetVersion) .join(db.DatasetFile) .where(db.Linkhash.hash == hash_value, db.Linkhash.expires_on > datetime.datetime.now(), db.DatasetFile.name == file)) if linkhash.count() > 0: logging.debug("Linkhash valid") # Get temporary user from hash_value user = (db.User .select(db.User) .join(db.Linkhash) .where(db.Linkhash.hash == hash_value) ).get() super().get(dataset, file, ds_version, user) else: logging.debug("Linkhash invalid") self.send_error(status_code=403)
[docs]class AngularTemplate(UnsafeHandler):
[docs] def initialize(self, path): self.root = path
[docs] def get(self, path): self.render(self.root + path)