From f844bd99c7242d8a7817e43c55a40e1d3ed83fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Mon, 24 Feb 2014 14:13:55 +0100 Subject: [PATCH] Properly authenticate log file downloads on Tornado as well Only allow for users with role admin. --- src/octoprint/server/__init__.py | 19 +++++++++++++++++-- src/octoprint/server/util.py | 12 ++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 79689adc..51bf09f9 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -2,6 +2,8 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +import flask +import tornado.wsgi from sockjs.tornado import SockJSRouter from flask import Flask, render_template, send_from_directory, make_response from flask.ext.login import LoginManager @@ -27,7 +29,7 @@ principals = Principal(app) admin_permission = Permission(RoleNeed("admin")) user_permission = Permission(RoleNeed("user")) - +# only import the octoprint stuff down here, as it might depend on things defined above to be initialized already from octoprint.server.util import LargeResponseHandler, ReverseProxied, restricted_access, PrinterStateConnection, admin_validator from octoprint.printer import Printer, getConnectionOptions from octoprint.settings import settings @@ -174,10 +176,23 @@ class Server(): self._router = SockJSRouter(self._createSocketConnection, "/sockjs") + def admin_access_validation(request): + """ + Creates a custom wsgi and Flask request context in order to be able to process user information + stored in the current session. + + :param request: The Tornado request for which to create the environment and context + """ + wsgi_environ = tornado.wsgi.WSGIContainer.environ(request) + with app.request_context(wsgi_environ): + app.session_interface.open_session(app, flask.request) + loginManager.reload_user() + admin_validator(flask.request) + self._tornado_app = Application(self._router.urls + [ (r"/downloads/timelapse/([^/]*\.mpg)", LargeResponseHandler, {"path": settings().getBaseFolder("timelapse"), "as_attachment": True}), (r"/downloads/files/local/([^/]*\.(gco|gcode))", LargeResponseHandler, {"path": settings().getBaseFolder("uploads"), "as_attachment": True}), - (r"/downloads/logs/([^/]*)", LargeResponseHandler, {"path": settings().getBaseFolder("logs"), "as_attachment": True}), + (r"/downloads/logs/([^/]*)", LargeResponseHandler, {"path": settings().getBaseFolder("logs"), "as_attachment": True, "access_validation": admin_access_validation}), (r".*", FallbackHandler, {"fallback": WSGIContainer(app.wsgi_app)}) ]) self._server = HTTPServer(self._tornado_app) diff --git a/src/octoprint/server/util.py b/src/octoprint/server/util.py index 2741441e..71149686 100644 --- a/src/octoprint/server/util.py +++ b/src/octoprint/server/util.py @@ -303,11 +303,19 @@ class LargeResponseHandler(StaticFileHandler): self.set_header("Content-Disposition", "attachment") -#~~ admin access validator for tornado +#~~ admin access validator for use with tornado -# TODO doesnt work for flask right now (no app context of course), try to figure out something def admin_validator(request): + """ + Validates that the given request is made by an admin user, identified either by API key or existing Flask + session. + + Must be executed in an existing Flask request context! + + :param request: The Flask request object + """ + apikey = _getApiKey(request) if settings().get(["api", "enabled"]) and apikey is not None: user = _getUserForApiKey(apikey)