From bb2fcd5b00e2a4033d4f0403b6010c0a1a72da95 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 20:25:04 -0500 Subject: [PATCH 01/33] Update flask, flask-{login,principal,babel,assets} versions to the latest, flask.ext. => flask_ --- setup.py | 10 ++--- src/octoprint/plugin/types.py | 2 +- .../plugins/announcements/__init__.py | 2 +- src/octoprint/plugins/corewizard/__init__.py | 2 +- .../plugins/pluginmanager/__init__.py | 2 +- .../plugins/softwareupdate/__init__.py | 4 +- src/octoprint/server/__init__.py | 8 ++-- src/octoprint/server/api/__init__.py | 4 +- src/octoprint/server/api/languages.py | 2 +- src/octoprint/server/api/system.py | 2 +- src/octoprint/server/api/users.py | 2 +- src/octoprint/server/util/flask.py | 42 +++++++++---------- src/octoprint/users.py | 4 +- 13 files changed, 43 insertions(+), 43 deletions(-) diff --git a/setup.py b/setup.py index 49b2cb9c..eaa06d68 100644 --- a/setup.py +++ b/setup.py @@ -14,15 +14,15 @@ import octoprint_setuptools # Requirements for our application INSTALL_REQUIRES = [ - "flask>=0.9,<0.11", + "flask>=0.11,<0.12", "werkzeug>=0.8.3,<0.9", "tornado>=4.0.2,<4.1", "sockjs-tornado>=1.0.2,<1.1", "PyYAML>=3.10,<3.11", - "Flask-Login>=0.2.2,<0.3", - "Flask-Principal>=0.3.5,<0.4", - "Flask-Babel>=0.9,<0.10", - "Flask-Assets>=0.10,<0.11", + "Flask-Login>=0.3,<0.4", + "Flask-Principal>=0.4,<0.5", + "Flask-Babel>=0.11,<0.12", + "Flask-Assets>=0.12,<0.13", "markdown>=2.6.4,<2.7", "pyserial>=2.7,<2.8", "netaddr>=0.7.17,<0.8", diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index 2bcc9f3b..f9659e7d 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -1373,7 +1373,7 @@ class SettingsPlugin(OctoPrintPlugin): :return: the current settings of the plugin, as a dictionary """ - from flask.ext.login import current_user + from flask_login import current_user data = self._settings.get_all_data() if self.config_version_key in data: diff --git a/src/octoprint/plugins/announcements/__init__.py b/src/octoprint/plugins/announcements/__init__.py index 890e8291..3cc03c10 100644 --- a/src/octoprint/plugins/announcements/__init__.py +++ b/src/octoprint/plugins/announcements/__init__.py @@ -20,7 +20,7 @@ import flask from octoprint.server import admin_permission from octoprint.server.util.flask import restricted_access, with_revalidation_checking, check_etag -from flask.ext.babel import gettext +from flask_babel import gettext class AnnouncementPlugin(octoprint.plugin.AssetPlugin, octoprint.plugin.SettingsPlugin, diff --git a/src/octoprint/plugins/corewizard/__init__.py b/src/octoprint/plugins/corewizard/__init__.py index 1a6b4e03..41dc4a13 100644 --- a/src/octoprint/plugins/corewizard/__init__.py +++ b/src/octoprint/plugins/corewizard/__init__.py @@ -9,7 +9,7 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms import octoprint.plugin -from flask.ext.babel import gettext +from flask_babel import gettext class CoreWizardPlugin(octoprint.plugin.AssetPlugin, diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index 51f002a8..c18ea2db 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -15,7 +15,7 @@ from octoprint.server import admin_permission, VERSION from octoprint.util.pip import LocalPipCaller, UnknownPip from flask import jsonify, make_response -from flask.ext.babel import gettext +from flask_babel import gettext import logging import sarge diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index 7a3ac351..a156f419 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -491,7 +491,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, ##~~ TemplatePlugin API def get_template_configs(self): - from flask.ext.babel import gettext + from flask_babel import gettext return [ dict(type="settings", name=gettext("Software Update")) ] @@ -806,7 +806,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, result = dict(check) if target == "octoprint": - from flask.ext.babel import gettext + from flask_babel import gettext result["displayName"] = check.get("displayName", gettext("OctoPrint")) result["displayVersion"] = check.get("displayVersion", "{octoprint_version}") diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 17308a26..aca26908 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -8,10 +8,10 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import uuid from sockjs.tornado import SockJSRouter from flask import Flask, g, request, session, Blueprint, Request, Response -from flask.ext.login import LoginManager, current_user -from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded, UserNeed -from flask.ext.babel import Babel, gettext, ngettext -from flask.ext.assets import Environment, Bundle +from flask_login import LoginManager, current_user +from flask_principal import Principal, Permission, RoleNeed, identity_loaded, UserNeed +from flask_babel import Babel, gettext, ngettext +from flask_assets import Environment, Bundle from babel import Locale from watchdog.observers import Observer from watchdog.observers.polling import PollingObserver diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index 6f6f37d5..5e7afb35 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -10,8 +10,8 @@ import netaddr import sarge from flask import Blueprint, request, jsonify, abort, current_app, session, make_response, g -from flask.ext.login import login_user, logout_user, current_user -from flask.ext.principal import Identity, identity_changed, AnonymousIdentity +from flask_login import login_user, logout_user, current_user +from flask_principal import Identity, identity_changed, AnonymousIdentity import octoprint.util as util import octoprint.users diff --git a/src/octoprint/server/api/languages.py b/src/octoprint/server/api/languages.py index e0749658..3d5f1f2c 100644 --- a/src/octoprint/server/api/languages.py +++ b/src/octoprint/server/api/languages.py @@ -26,7 +26,7 @@ from octoprint.server.util.flask import restricted_access from octoprint.plugin import plugin_manager -from flask.ext.babel import Locale +from flask_babel import Locale @api.route("/languages", methods=["GET"]) @restricted_access diff --git a/src/octoprint/server/api/system.py b/src/octoprint/server/api/system.py index 8af7a24c..fbdec959 100644 --- a/src/octoprint/server/api/system.py +++ b/src/octoprint/server/api/system.py @@ -9,7 +9,7 @@ import logging import sarge from flask import request, make_response, jsonify, url_for -from flask.ext.babel import gettext +from flask_babel import gettext from octoprint.settings import settings as s diff --git a/src/octoprint/server/api/users.py b/src/octoprint/server/api/users.py index 73233f5e..8335315f 100644 --- a/src/octoprint/server/api/users.py +++ b/src/octoprint/server/api/users.py @@ -7,7 +7,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms from flask import request, jsonify, abort, make_response from werkzeug.exceptions import BadRequest -from flask.ext.login import current_user +from flask_login import current_user import octoprint.users as users diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index f4281ee1..ee7fe883 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -8,9 +8,9 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import tornado.web import flask -import flask.ext.login -import flask.ext.principal -import flask.ext.assets +import flask_login +import flask_principal +import flask_assets import webassets.updater import webassets.utils import functools @@ -40,7 +40,7 @@ def enable_additional_translations(default_locale="en", additional_folders=None) import os from flask import _request_ctx_stack from babel import support, Locale - import flask.ext.babel + import flask_babel if additional_folders is None: additional_folders = [] @@ -85,7 +85,7 @@ def enable_additional_translations(default_locale="en", additional_folders=None) return None translations = getattr(ctx, 'babel_translations', None) if translations is None: - locale = flask.ext.babel.get_locale() + locale = flask_babel.get_locale() translations = support.Translations() if str(locale) != default_locale: @@ -123,8 +123,8 @@ def enable_additional_translations(default_locale="en", additional_folders=None) ctx.babel_translations = translations return translations - flask.ext.babel.Babel.list_translations = fixed_list_translations - flask.ext.babel.get_translations = fixed_get_translations + flask_babel.Babel.list_translations = fixed_list_translations + flask_babel.get_translations = fixed_get_translations def fix_webassets_cache(): from webassets import cache @@ -426,12 +426,12 @@ class OctoPrintFlaskResponse(flask.Response): def passive_login(): if octoprint.server.userManager.enabled: - user = octoprint.server.userManager.login_user(flask.ext.login.current_user) + user = octoprint.server.userManager.login_user(flask_login.current_user) else: - user = flask.ext.login.current_user + user = flask_login.current_user - if user is not None and not user.is_anonymous(): - flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id())) + if user is not None and not user.is_anonymous: + flask_principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask_principal.Identity(user.get_id())) if hasattr(user, "get_session"): flask.session["usersession.id"] = user.get_session() flask.g.user = user @@ -453,8 +453,8 @@ def passive_login(): user = octoprint.server.userManager.login_user(user) flask.session["usersession.id"] = user.get_session() flask.g.user = user - flask.ext.login.login_user(user) - flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id())) + flask_login.login_user(user) + flask_principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask_principal.Identity(user.get_id())) return flask.jsonify(user.asDict()) except: logger = logging.getLogger(__name__) @@ -999,14 +999,14 @@ def _get_flask_user_from_request(request): :return: the user or None if no user could be determined """ import octoprint.server.util - import flask.ext.login + import flask_login from octoprint.settings import settings apikey = octoprint.server.util.get_api_key(request) if settings().getBoolean(["api", "enabled"]) and apikey is not None: user = octoprint.server.util.get_user_for_apikey(apikey) else: - user = flask.ext.login.current_user + user = flask_login.current_user return user @@ -1061,7 +1061,7 @@ def restricted_access(func): apikey = octoprint.server.util.get_api_key(flask.request) if apikey == octoprint.server.UI_API_KEY: # UI API key => call regular login_required decorator, we are using browser sessions here - return flask.ext.login.login_required(func)(*args, **kwargs) + return flask_login.login_required(func)(*args, **kwargs) # try to determine user for key user = octoprint.server.util.get_user_for_apikey(apikey) @@ -1069,11 +1069,11 @@ def restricted_access(func): # no user or no key => go away return flask.make_response("Invalid API key", 401) - if not flask.ext.login.login_user(user, remember=False): + if not flask_login.login_user(user, remember=False): # user for API key could not be logged in => go away return flask.make_response("Invalid API key", 401) - flask.ext.principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask.ext.principal.Identity(user.get_id())) + flask_principal.identity_changed.send(flask.current_app._get_current_object(), identity=flask_principal.Identity(user.get_id())) return func(*args, **kwargs) return decorated_view @@ -1172,7 +1172,7 @@ def get_json_command_from_request(request, valid_commands): ##~~ Flask-Assets resolver with plugin asset support -class PluginAssetResolver(flask.ext.assets.FlaskResolver): +class PluginAssetResolver(flask_assets.FlaskResolver): def split_prefix(self, ctx, item): app = ctx.environment._app @@ -1181,14 +1181,14 @@ class PluginAssetResolver(flask.ext.assets.FlaskResolver): prefix, plugin, name = item.split("/", 2) blueprint = prefix + "." + plugin - directory = flask.ext.assets.get_static_folder(app.blueprints[blueprint]) + directory = flask_assets.get_static_folder(app.blueprints[blueprint]) item = name endpoint = blueprint + ".static" return directory, item, endpoint except (ValueError, KeyError): pass - return flask.ext.assets.FlaskResolver.split_prefix(self, ctx, item) + return flask_assets.FlaskResolver.split_prefix(self, ctx, item) def resolve_output_to_path(self, ctx, target, bundle): import os diff --git a/src/octoprint/users.py b/src/octoprint/users.py index a615d222..e5761773 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -5,8 +5,8 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" -from flask.ext.login import UserMixin -from flask.ext.principal import Identity +from flask_login import UserMixin +from flask_principal import Identity from werkzeug.local import LocalProxy import hashlib import os From 87d65c150b043efa20a666f7bbf326492abd5ee9 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 20:30:12 -0500 Subject: [PATCH 02/33] Update werkzeug --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eaa06d68..950f4b28 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ import octoprint_setuptools # Requirements for our application INSTALL_REQUIRES = [ "flask>=0.11,<0.12", - "werkzeug>=0.8.3,<0.9", + "werkzeug>=0.11.1,<0.12", "tornado>=4.0.2,<4.1", "sockjs-tornado>=1.0.2,<1.1", "PyYAML>=3.10,<3.11", From fbb881bb426d6cd398fa039731545901dc8f59f4 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:00:14 -0500 Subject: [PATCH 03/33] Update Tornado, adapt CustomHTTPServer to new usage --- setup.py | 2 +- src/octoprint/server/util/tornado.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 950f4b28..26c473d6 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ import octoprint_setuptools INSTALL_REQUIRES = [ "flask>=0.11,<0.12", "werkzeug>=0.11.1,<0.12", - "tornado>=4.0.2,<4.1", + "tornado>=4.4.2,<4.5", "sockjs-tornado>=1.0.2,<1.1", "PyYAML>=3.10,<3.11", "Flask-Login>=0.3,<0.4", diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 12ba3b9b..54eb0955 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -672,8 +672,10 @@ class CustomHTTPServer(tornado.httpserver.HTTPServer): ``default_max_body_size`` is the default maximum body size to apply if no specific one from ``max_body_sizes`` matches. """ + def __init__(self, *args, **kwargs): + pass - def __init__(self, request_callback, no_keep_alive=False, io_loop=None, + def initialize(self, request_callback, no_keep_alive=False, io_loop=None, xheaders=False, ssl_options=None, protocol=None, decompress_request=False, chunk_size=None, max_header_size=None, From 9a6da3f46f65462a26349bfea8671733772ef07b Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:03:29 -0500 Subject: [PATCH 04/33] Update PyYAML --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 26c473d6..0a9ae65e 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ INSTALL_REQUIRES = [ "werkzeug>=0.11.1,<0.12", "tornado>=4.4.2,<4.5", "sockjs-tornado>=1.0.2,<1.1", - "PyYAML>=3.10,<3.11", + "PyYAML>=3.12,<3.13", "Flask-Login>=0.3,<0.4", "Flask-Principal>=0.4,<0.5", "Flask-Babel>=0.11,<0.12", From 23a28012841baf3f21302ba97757f546b2a3270e Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:08:45 -0500 Subject: [PATCH 05/33] Update pyserial --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0a9ae65e..3ada2056 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ INSTALL_REQUIRES = [ "Flask-Babel>=0.11,<0.12", "Flask-Assets>=0.12,<0.13", "markdown>=2.6.4,<2.7", - "pyserial>=2.7,<2.8", + "pyserial>=3.1.1,<3.2", "netaddr>=0.7.17,<0.8", "watchdog>=0.8.3,<0.9", "sarge>=0.1.4,<0.2", From e15ca55558324f6af5084291ca65883a56ce9491 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:11:33 -0500 Subject: [PATCH 06/33] Update rsa --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3ada2056..ca0091e9 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ INSTALL_REQUIRES = [ "sarge>=0.1.4,<0.2", "netifaces>=0.10,<0.11", "pylru>=1.0.9,<1.1", - "rsa>=3.2,<3.3", + "rsa>=3.4,<3.5", "pkginfo>=1.2.1,<1.3", "requests>=2.7,<2.8", "semantic_version>=2.4.2,<2.5", From e998edd083bf2715706339e3b35f3a9c4f8353eb Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:12:46 -0500 Subject: [PATCH 07/33] Update pkginfo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ca0091e9..bd8a6841 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ INSTALL_REQUIRES = [ "netifaces>=0.10,<0.11", "pylru>=1.0.9,<1.1", "rsa>=3.4,<3.5", - "pkginfo>=1.2.1,<1.3", + "pkginfo>=1.3.2,<1.4", "requests>=2.7,<2.8", "semantic_version>=2.4.2,<2.5", "psutil>=3.2.1,<3.3", From 599e24634cf5e7c5f14df8729cfe51f16957066f Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:16:31 -0500 Subject: [PATCH 08/33] Update requests --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bd8a6841..0cd0ac2b 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ INSTALL_REQUIRES = [ "pylru>=1.0.9,<1.1", "rsa>=3.4,<3.5", "pkginfo>=1.3.2,<1.4", - "requests>=2.7,<2.8", + "requests>=2.11.1,<2.12", "semantic_version>=2.4.2,<2.5", "psutil>=3.2.1,<3.3", "Click>=6.2,<6.3", From 7d091d18ee552e1124367352c30d14944f10351d Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:19:54 -0500 Subject: [PATCH 09/33] Update semantic_version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0cd0ac2b..4c7d4894 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ INSTALL_REQUIRES = [ "rsa>=3.4,<3.5", "pkginfo>=1.3.2,<1.4", "requests>=2.11.1,<2.12", - "semantic_version>=2.4.2,<2.5", + "semantic_version>=2.6.0,<2.7", "psutil>=3.2.1,<3.3", "Click>=6.2,<6.3", "awesome-slugify>=1.6.5,<1.7", From bc54ddf19f26e0cf0526d25d5a654a7eb67d6e08 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:25:48 -0500 Subject: [PATCH 10/33] Update psutil --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4c7d4894..52965618 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ INSTALL_REQUIRES = [ "pkginfo>=1.3.2,<1.4", "requests>=2.11.1,<2.12", "semantic_version>=2.6.0,<2.7", - "psutil>=3.2.1,<3.3", + "psutil>=4.3.1,<4.4", "Click>=6.2,<6.3", "awesome-slugify>=1.6.5,<1.7", "feedparser>=5.2.1,<5.3", From 943d7ce31a02f3cec8894e92b5797c58b1b766dc Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 6 Oct 2016 21:35:43 -0500 Subject: [PATCH 11/33] Update Click --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 52965618..41e9a56d 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ INSTALL_REQUIRES = [ "requests>=2.11.1,<2.12", "semantic_version>=2.6.0,<2.7", "psutil>=4.3.1,<4.4", - "Click>=6.2,<6.3", + "Click>=6.6,<6.7", "awesome-slugify>=1.6.5,<1.7", "feedparser>=5.2.1,<5.3", "chainmap>=1.0.2,<1.1", From 37f51ef983fb38ebc6f761842add961c446cdee0 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Fri, 7 Oct 2016 11:37:36 -0500 Subject: [PATCH 12/33] Botched the initial Flask-Login update -- is_active(), is_authenticated(), and is_anonymous() got converted into properties, so we should do the same --- src/octoprint/plugin/types.py | 4 ++-- src/octoprint/server/__init__.py | 4 ++-- src/octoprint/server/api/__init__.py | 4 ++-- src/octoprint/server/api/users.py | 12 ++++++------ src/octoprint/server/util/flask.py | 4 ++-- src/octoprint/users.py | 13 ++++++++----- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index f9659e7d..dd17d7a7 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -1400,8 +1400,8 @@ class SettingsPlugin(OctoPrintPlugin): if key in node: node[key] = None - conditions = dict(user=lambda: current_user is not None and not current_user.is_anonymous(), - admin=lambda: current_user is not None and not current_user.is_anonymous() and current_user.is_admin(), + conditions = dict(user=lambda: current_user is not None and not current_user.is_anonymous, + admin=lambda: current_user is not None and not current_user.is_anonymous and current_user.is_admin, never=lambda: False) for level, condition in conditions.items(): diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index aca26908..bbedc609 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -86,9 +86,9 @@ def on_identity_loaded(sender, identity): return identity.provides.add(UserNeed(user.get_id())) - if user.is_user(): + if user.is_user: identity.provides.add(RoleNeed("user")) - if user.is_admin(): + if user.is_admin: identity.provides.add(RoleNeed("admin")) def load_user(id): diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index 5e7afb35..7602ae18 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -60,7 +60,7 @@ def pluginData(name): return make_response("More than one api provider registered for {name}, can't proceed".format(name=name), 500) api_plugin = api_plugins[0] - if api_plugin.is_api_adminonly() and not current_user.is_admin(): + if api_plugin.is_api_adminonly() and not current_user.is_admin: return make_response("Forbidden", 403) response = api_plugin.on_api_get(request) @@ -87,7 +87,7 @@ def pluginCommand(name): if valid_commands is None: return make_response("Method not allowed", 405) - if api_plugin.is_api_adminonly() and not current_user.is_admin(): + if api_plugin.is_api_adminonly() and not current_user.is_admin: return make_response("Forbidden", 403) command, data, response = get_json_command_from_request(request, valid_commands) diff --git a/src/octoprint/server/api/users.py b/src/octoprint/server/api/users.py index 8335315f..fbee5c16 100644 --- a/src/octoprint/server/api/users.py +++ b/src/octoprint/server/api/users.py @@ -72,7 +72,7 @@ def getUser(username): if not userManager.enabled: return jsonify(SUCCESS) - if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()): + if current_user is not None and not current_user.is_anonymous and (current_user.get_name() == username or current_user.is_admin): user = userManager.findUser(username) if user is not None: return jsonify(user.asDict()) @@ -133,7 +133,7 @@ def changePasswordForUser(username): if not userManager.enabled: return jsonify(SUCCESS) - if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()): + if current_user is not None and not current_user.is_anonymous and (current_user.get_name() == username or current_user.is_admin): if not "application/json" in request.headers["Content-Type"]: return make_response("Expected content-type JSON", 400) @@ -161,7 +161,7 @@ def getSettingsForUser(username): if not userManager.enabled: return jsonify(SUCCESS) - if current_user is None or current_user.is_anonymous() or (current_user.get_name() != username and not current_user.is_admin()): + if current_user is None or current_user.is_anonymous or (current_user.get_name() != username and not current_user.is_admin): return make_response("Forbidden", 403) try: @@ -175,7 +175,7 @@ def changeSettingsForUser(username): if not userManager.enabled: return jsonify(SUCCESS) - if current_user is None or current_user.is_anonymous() or (current_user.get_name() != username and not current_user.is_admin()): + if current_user is None or current_user.is_anonymous or (current_user.get_name() != username and not current_user.is_admin): return make_response("Forbidden", 403) try: @@ -195,7 +195,7 @@ def deleteApikeyForUser(username): if not userManager.enabled: return jsonify(SUCCESS) - if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()): + if current_user is not None and not current_user.is_anonymous and (current_user.get_name() == username or current_user.is_admin): try: userManager.deleteApikey(username) except users.UnknownUser: @@ -211,7 +211,7 @@ def generateApikeyForUser(username): if not userManager.enabled: return jsonify(SUCCESS) - if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()): + if current_user is not None and not current_user.is_anonymous and (current_user.get_name() == username or current_user.is_admin): try: apikey = userManager.generateApiKey(username) except users.UnknownUser: diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index ee7fe883..d7aca094 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -971,7 +971,7 @@ def admin_validator(request): """ user = _get_flask_user_from_request(request) - if user is None or not user.is_authenticated() or not user.is_admin(): + if user is None or not user.is_authenticated or not user.is_admin: raise tornado.web.HTTPError(403) @@ -986,7 +986,7 @@ def user_validator(request): """ user = _get_flask_user_from_request(request) - if user is None or not user.is_authenticated(): + if user is None or not user.is_authenticated: raise tornado.web.HTTPError(403) diff --git a/src/octoprint/users.py b/src/octoprint/users.py index e5761773..1ce377a0 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -428,9 +428,9 @@ class User(UserMixin): def asDict(self): return { "name": self._username, - "active": self.is_active(), - "admin": self.is_admin(), - "user": self.is_user(), + "active": self.is_active, + "admin": self.is_admin, + "user": self.is_user, "apikey": self._apikey, "settings": self._settings } @@ -444,12 +444,15 @@ class User(UserMixin): def get_name(self): return self._username + @property def is_active(self): return self._active + @property def is_user(self): return "user" in self._roles + @property def is_admin(self): return "admin" in self._roles @@ -496,7 +499,7 @@ class User(UserMixin): return True def __repr__(self): - return "User(id=%s,name=%s,active=%r,user=%r,admin=%r)" % (self.get_id(), self.get_name(), self.is_active(), self.is_user(), self.is_admin()) + return "User(id=%s,name=%s,active=%r,user=%r,admin=%r)" % (self.get_id(), self.get_name(), self.is_active, self.is_user, self.is_admin) class SessionUser(User): def __init__(self, user): @@ -526,7 +529,7 @@ class SessionUser(User): return self._session def __repr__(self): - return "SessionUser(id=%s,name=%s,active=%r,user=%r,admin=%r,session=%s,created=%s)" % (self.get_id(), self.get_name(), self.is_active(), self.is_user(), self.is_admin(), self._session, self._created) + return "SessionUser(id=%s,name=%s,active=%r,user=%r,admin=%r,session=%s,created=%s)" % (self.get_id(), self.get_name(), self.is_active, self.is_user, self.is_admin, self._session, self._created) ##~~ DummyUser object to use when accessControl is disabled From 35d480bc482333e99f1785aca7259d7799dd4537 Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Tue, 25 Oct 2016 21:36:47 -0500 Subject: [PATCH 13/33] Clean up bogus whitespace changes/inconsistency --- src/octoprint/server/util/flask.py | 2 +- src/octoprint/server/util/tornado.py | 4 ++-- src/octoprint/users.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index d7aca094..a0292ad1 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -426,7 +426,7 @@ class OctoPrintFlaskResponse(flask.Response): def passive_login(): if octoprint.server.userManager.enabled: - user = octoprint.server.userManager.login_user(flask_login.current_user) + user = octoprint.server.userManager.login_user(flask_login.current_user) else: user = flask_login.current_user diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 54eb0955..e599d151 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -672,8 +672,8 @@ class CustomHTTPServer(tornado.httpserver.HTTPServer): ``default_max_body_size`` is the default maximum body size to apply if no specific one from ``max_body_sizes`` matches. """ - def __init__(self, *args, **kwargs): - pass + def __init__(self, *args, **kwargs): + pass def initialize(self, request_callback, no_keep_alive=False, io_loop=None, xheaders=False, ssl_options=None, protocol=None, diff --git a/src/octoprint/users.py b/src/octoprint/users.py index 1ce377a0..d7d4d1e4 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -444,15 +444,15 @@ class User(UserMixin): def get_name(self): return self._username - @property + @property def is_active(self): return self._active - @property + @property def is_user(self): return "user" in self._roles - @property + @property def is_admin(self): return "admin" in self._roles From f318cc5e284ef437e80350a61b137ae172eceadd Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 10 Nov 2016 13:12:02 -0600 Subject: [PATCH 14/33] Update 970880d, flask.ext.login -> flask_login --- src/octoprint/server/api/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index b4d862ae..0225ad81 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import logging from flask import request, jsonify, make_response -from flask.ext.login import current_user +from flask_login import current_user from werkzeug.exceptions import BadRequest from octoprint.events import eventManager, Events @@ -32,7 +32,7 @@ def _etag(lm=None): connection_options = printer.__class__.get_connection_options() plugins = sorted(octoprint.plugin.plugin_manager().enabled_plugins) - if current_user is not None and not current_user.is_anonymous(): + if current_user is not None and not current_user.is_anonymous: roles = sorted(current_user.roles) else: roles = [] From 153b5de1d43661408af96e6ff8588b6263f4d36c Mon Sep 17 00:00:00 2001 From: Juha-Jarmo Heinonen Date: Mon, 19 Dec 2016 20:35:04 +0200 Subject: [PATCH 15/33] Added /dev/ttyS* into serial ports to find. OctoPrint was unable to find any of the serial ports on my CubieBoard, which are all ttyS devices; this fixes that issue. --- AUTHORS.md | 1 + src/octoprint/util/comm.py | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index a85ff3f2..4ff4458d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -74,6 +74,7 @@ date of first contribution): * [Mathias Rangel Wulff](https://github.com/mathiasrw) * [Clemens Niemeyer](https://github.com/clemniem) * ["I-am-me"](https://github.com/I-am-me) + * [J-J Heinonen](https://github.com/jammi) OctoPrint started off as a fork of [Cura](https://github.com/daid/Cura) by [Daid Braam](https://github.com/daid). Parts of its communication layer and diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index dd0ab9c6..71cc3fd5 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -144,6 +144,7 @@ def serialList(): + glob.glob("/dev/tty.usb*") \ + glob.glob("/dev/cu.*") \ + glob.glob("/dev/cuaU*") \ + + glob.glob("/dev/ttyS*") \ + glob.glob("/dev/rfcomm*") additionalPorts = settings().get(["serial", "additionalPorts"]) From 8a8b88af13bb86fc8391e80cd7bcf9a035d2fbd1 Mon Sep 17 00:00:00 2001 From: Marc Hannappel Date: Wed, 21 Dec 2016 09:44:24 +0100 Subject: [PATCH 16/33] Fix folders doesn't get listed when performing a filtering in file section, issue #1667 --- src/octoprint/static/js/app/viewmodels/files.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/octoprint/static/js/app/viewmodels/files.js b/src/octoprint/static/js/app/viewmodels/files.js index 24695b2d..2563ebe8 100644 --- a/src/octoprint/static/js/app/viewmodels/files.js +++ b/src/octoprint/static/js/app/viewmodels/files.js @@ -708,11 +708,12 @@ $(function() { return false; } - if (entry["type"] == "folder" && entry["children"]) { + var success = entry["name"].toLocaleLowerCase().indexOf(query) > -1; + if (!success && entry["type"] == "folder" && entry["children"]) { return _.any(entry["children"], recursiveSearch); - } else { - return entry["name"].toLocaleLowerCase().indexOf(query) > -1; } + + return success; }; self.listHelper.changeSearchFunction(recursiveSearch); From bcb74d01b63ba18ddf071320ed1007c1a4ef9da8 Mon Sep 17 00:00:00 2001 From: Marc Hannappel Date: Wed, 21 Dec 2016 10:51:19 +0100 Subject: [PATCH 17/33] Added active tab using hash from URL feature, as requested in issue #203 --- src/octoprint/static/js/app/main.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 86221ca4..d762f1e5 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -478,6 +478,15 @@ $(function() { // reload overlay $("#reloadui_overlay_reload").click(function() { location.reload(); }); + var changeTab = function() + { + var hashtag = window.location.hash; + + var tab = $('#tabs a[href="' + hashtag + '"]'); + tab.tab("show"); + onTabChange(hashtag); + } + //~~ view model binding var bindViewModels = function() { @@ -566,6 +575,15 @@ $(function() { callViewModels(allViewModels, "onBrowserTabVisibilityChange", [status]); }); + $(window).on("hashchange", function() { + changeTab(); + }); + + if (window.location.hash != "") + { + changeTab(); + } + log.info("Application startup complete"); }; From b3f35c579b37bbfd7e02671a7572e26289fe2f2c Mon Sep 17 00:00:00 2001 From: Marc Hannappel Date: Wed, 21 Dec 2016 10:56:11 +0100 Subject: [PATCH 18/33] Little update to the active tab using hash from URL, forgot to add a check if the hash is valid, fixed this now --- src/octoprint/static/js/app/main.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index d762f1e5..80b1701e 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -481,10 +481,12 @@ $(function() { var changeTab = function() { var hashtag = window.location.hash; - var tab = $('#tabs a[href="' + hashtag + '"]'); - tab.tab("show"); - onTabChange(hashtag); + if (tab.length) + { + tab.tab("show"); + onTabChange(hashtag); + } } //~~ view model binding From 639619ff939c00befadbc5ded213200cae431a9c Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Mon, 9 Jan 2017 08:51:52 -0600 Subject: [PATCH 19/33] Partially revert 57de36a --- src/octoprint/server/__init__.py | 3 +-- src/octoprint/server/util/tornado.py | 30 ---------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 4c60edc9..e9a432c7 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -168,8 +168,7 @@ class Server(object): self._logger = logging.getLogger(__name__) pluginManager = self._plugin_manager - # monkey patch a bunch of stuff - util.tornado.fix_ioloop_scheduling() + # monkey patch some stuff util.flask.enable_additional_translations(additional_folders=[self._settings.getBaseFolder("translations")]) # setup app diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index e599d151..8325bcaf 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -29,36 +29,6 @@ import tornado.util import octoprint.util -#~~ Monkey patching - - -def fix_ioloop_scheduling(): - """ - This monkey patches tornado's :meth:``tornado.ioloop.PeriodicCallback._schedule_next`` method so it no longer - blocks for long times on slow machines (RPi) when the system time happens to change by a large amount (e.g. due to - the first ever contact to an NTP server). - - Patch by @nosyjoe on Github. See this PR against tornado: https://github.com/tornadoweb/tornado/pull/1290 - """ - - import math - - # patched implementation taken from PR - def _schedule_next(self): - if self._running: - current_time = self.io_loop.time() - - if self._next_timeout <= current_time: - callback_time_sec = self.callback_time / 1000.0 - self._next_timeout += (math.floor((current_time - self._next_timeout) / callback_time_sec) + 1) * callback_time_sec - - self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run) - - # replace original implementation with patched version - import tornado.ioloop - tornado.ioloop.PeriodicCallback._schedule_next = _schedule_next - - #~~ WSGI middleware From 72f3fa6eda99121169b279d79ec28ef936996902 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Tue, 10 Jan 2017 23:36:55 -0800 Subject: [PATCH 20/33] Changed old comments to not claim a method does more than it really does, because comments are there to clear up confusion, might as well start here. poll_temperature and poll_sd_status are called by a RepeatedTimer now, the methods don't re-enqueue themselves. --- src/octoprint/util/comm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 71cc3fd5..fb1e1118 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -1545,7 +1545,7 @@ class MachineCom(object): def _poll_temperature(self): """ - Polls the temperature after the temperature timeout, re-enqueues itself. + Polls the temperature. If the printer is not operational, closing the connection, not printing from sd, busy with a long running command or heating, no poll will be done. @@ -1556,7 +1556,7 @@ class MachineCom(object): def _poll_sd_status(self): """ - Polls the sd printing status after the sd status timeout, re-enqueues itself. + Polls the sd printing status. If the printer is not operational, closing the connection, not printing from sd, busy with a long running command or heating, no poll will be done. From ce38ef628908db0b40071f17f834a6687dc182f4 Mon Sep 17 00:00:00 2001 From: Eyal Soha Date: Thu, 2 Feb 2017 10:47:41 +0200 Subject: [PATCH 21/33] add same device to the slicing API --- src/octoprint/server/api/slicing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/octoprint/server/api/slicing.py b/src/octoprint/server/api/slicing.py index 3aa8a33d..824faf02 100644 --- a/src/octoprint/server/api/slicing.py +++ b/src/octoprint/server/api/slicing.py @@ -72,6 +72,7 @@ def slicingListAll(): result[slicer] = dict( key=slicer, displayName=slicer_impl.get_slicer_properties()["name"], + sameDevice=slicer_impl.get_slicer_properties()["same_device"], default=default_slicer == slicer, configured=slicer_impl.is_slicer_configured(), profiles=_getSlicingProfilesData(slicer), From fea07917fae2079c02f43adf1215877e0f94c534 Mon Sep 17 00:00:00 2001 From: Eyal Soha Date: Sun, 12 Feb 2017 12:35:57 +0200 Subject: [PATCH 22/33] add sameDevice to SlicingViewModel --- src/octoprint/static/js/app/viewmodels/slicing.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/octoprint/static/js/app/viewmodels/slicing.js b/src/octoprint/static/js/app/viewmodels/slicing.js index 8e39b506..ae98bed7 100644 --- a/src/octoprint/static/js/app/viewmodels/slicing.js +++ b/src/octoprint/static/js/app/viewmodels/slicing.js @@ -204,7 +204,8 @@ $(function() { name: name, configured: slicer.configured, sourceExtensions: slicer.extensions.source, - destinationExtensions: slicer.extensions.destination + destinationExtensions: slicer.extensions.destination, + sameDevice: slicer.sameDevice }; self.slicers.push(props); }); From e82efd24d347d04806b01dac0394b797a06e5794 Mon Sep 17 00:00:00 2001 From: Greg Hulands Date: Mon, 20 Feb 2017 11:02:07 -0800 Subject: [PATCH 23/33] Issue #1780: Safari Webcam Fix. --- .../static/js/app/viewmodels/control.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/octoprint/static/js/app/viewmodels/control.js b/src/octoprint/static/js/app/viewmodels/control.js index 3879cd6c..d878eef5 100644 --- a/src/octoprint/static/js/app/viewmodels/control.js +++ b/src/octoprint/static/js/app/viewmodels/control.js @@ -362,9 +362,21 @@ $(function() { self.onSettingsBeforeSave = self.updateRotatorWidth; + self._isSafari = function() { + var is_chrome = navigator.userAgent.indexOf('Chrome') > -1; + var is_safari = navigator.userAgent.indexOf("Safari") > -1; + return is_safari && !is_chrome; + } + self._disableWebcam = function() { // only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause // more load by the constant connection creation than by the actual webcam stream + + // safari bug doesn't release the mjpeg stream, so we just disable this for safari. + if (self._isSafari()) { + return; + } + self.webcamDisableTimeout = setTimeout(function () { $("#webcam_image").attr("src", ""); }, 5000); @@ -380,6 +392,12 @@ $(function() { } var webcamImage = $("#webcam_image"); var currentSrc = webcamImage.attr("src"); + + // safari bug doesn't release the mjpeg stream, so we just set it up the once + if (self._isSafari() && currentSrc != undefined) { + return; + } + var newSrc = self.settings.webcam_streamUrl(); if (currentSrc != newSrc) { if (newSrc.lastIndexOf("?") > -1) { From 0adb62b485033b154f86891a59bf5b2d8eda3888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 11:55:35 +0100 Subject: [PATCH 24/33] Compatibility layer for plugins depending on flask.ext Logs a deprecation warning when anything from flask.ext is accessed, imports flask_ instead and returns it. To be removed again in OctoPrint 1.5.0 --- src/octoprint/server/__init__.py | 4 +++- src/octoprint/server/util/flask.py | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index abc717ff..5adbcf2e 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -609,7 +609,9 @@ class Server(object): return Locale.parse(request.accept_languages.best_match(LANGUAGES)) def _setup_app(self, app): - from octoprint.server.util.flask import ReverseProxiedEnvironment, OctoPrintFlaskRequest, OctoPrintFlaskResponse + from octoprint.server.util.flask import ReverseProxiedEnvironment, OctoPrintFlaskRequest, OctoPrintFlaskResponse, deprecate_flaskext + + deprecate_flaskext() # TODO: remove in OctoPrint 1.5.0 s = settings() diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index 9fc0defd..328bb6fa 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -229,6 +229,27 @@ def fix_webassets_filtertool(): FilterTool._wrap_cache = fixed_wrap_cache +# TODO: Remove compatibility layer in OctoPrint 1.5.0 +def deprecate_flaskext(): + import flask + import importlib + + class FlaskExtDeprecator(object): + + def __getattr__(self, item): + old_name = "flask.ext.{}".format(item) + new_name = "flask_{}".format(item) + module = importlib.import_module(new_name) + + from warnings import warn + message = "The {old} import is deprecated in Flask versions >= 0.11, which OctoPrint now uses. " + \ + "Import {new} instead. This compatibility layer will be removed in OctoPrint 1.5.0." + warn(DeprecationWarning(message.format(old=old_name, new=new_name)), stacklevel=2) + + return module + + flask.ext = FlaskExtDeprecator() + #~~ WSGI environment wrapper for reverse proxying class ReverseProxiedEnvironment(object): @@ -1188,7 +1209,10 @@ def get_json_command_from_request(request, valid_commands): if content_type is None or not "application/json" in content_type: return None, None, make_response("Expected content-type JSON", 400) - data = request.json + data = request.get_json() + if data is None: + return make_response("Malformed JSON body in request", 400) + if not "command" in data.keys() or not data["command"] in valid_commands.keys(): return None, None, make_response("Expected valid command", 400) From b7a0f5bee56ab9fb39df45167322582a10ddf8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 12:00:51 +0100 Subject: [PATCH 25/33] Compatibility layer for plugins depending on user.is_anonymous() etc Logs a deprecation warning when any of the made-property fields is accessed as a getter and return value. Implements __eq__, __ne__, __bool__ and __nonzero__ to allow things like "user.is_anonymous == True" or "not user.is_anonymous" is used. Careful with serialization to json, needs "bool(...)" wrapper to not cause an exception (or inclusion in a custom serializer). Affected fields: * is_anonymous * is_authenticated * is_active * is_user * is_admin To be removed again in OctoPrint 1.5.0 --- src/octoprint/server/__init__.py | 1 + src/octoprint/users.py | 97 +++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 5adbcf2e..a574776b 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -336,6 +336,7 @@ class Server(object): loginManager = LoginManager() loginManager.session_protection = "strong" loginManager.user_callback = load_user + loginManager.anonymous_user = users.AnonymousUser # TODO: remove in 1.5.0 if not userManager.enabled: loginManager.anonymous_user = users.DummyUser principals.identity_loaders.appendleft(users.dummy_identity_loader) diff --git a/src/octoprint/users.py b/src/octoprint/users.py index 1803104b..fb3fc83d 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -5,7 +5,7 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" -from flask_login import UserMixin +from flask_login import UserMixin, AnonymousUserMixin from flask_principal import Identity from werkzeug.local import LocalProxy import hashlib @@ -413,6 +413,65 @@ class UnknownRole(Exception): ##~~ User object +class MethodReplacedByBooleanProperty(object): + + def __init__(self, name, message, getter): + self._name = name + self._message = message + self._getter = getter + + @property + def _attr(self): + return self._getter() + + def __call__(self): + from warnings import warn + warn(DeprecationWarning(self._message.format(name=self._name)), stacklevel=2) + return self._attr + + def __eq__(self, other): + return self._attr == other + + def __ne__(self, other): + return self._attr != other + + def __bool__(self): + # Python 3 + return self._attr + + def __nonzero__(self): + # Python 2 + return self._attr + + def __hash__(self): + return hash(self._attr) + + def __repr__(self): + return "MethodReplacedByProperty({}, {}, {})".format(self._name, self._message, self._getter) + + def __str__(self): + return str(self._attr) + + +# TODO: Remove compatibility layer in OctoPrint 1.5.0 +class FlaskLoginMethodReplacedByBooleanProperty(MethodReplacedByBooleanProperty): + + def __init__(self, name, getter): + message = "{name} is now a property in Flask-Login versions >= 0.3.0, which OctoPrint now uses. " + \ + "Use {name} instead of {name}(). This compatibility layer will be removed in OctoPrint 1.5.0." + MethodReplacedByBooleanProperty.__init__(self, name, message, getter) + + +# TODO: Remove compatibility layer in OctoPrint 1.5.0 +class OctoPrintUserMethodReplacedByBooleanProperty(MethodReplacedByBooleanProperty): + + def __init__(self, name, getter): + message = "{name} is now a property for consistency reasons with Flask-Login versions >= 0.3.0, which " + \ + "OctoPrint now uses. Use {name} instead of {name}(). This compatibility layer will be removed " + \ + "in OctoPrint 1.5.0." + MethodReplacedByBooleanProperty.__init__(self, name, message, getter) + + class User(UserMixin): def __init__(self, username, passwordHash, active, roles, apikey=None, settings=None): self._username = username @@ -428,9 +487,9 @@ class User(UserMixin): def asDict(self): return { "name": self._username, - "active": self.is_active, - "admin": self.is_admin, - "user": self.is_user, + "active": bool(self.is_active), + "admin": bool(self.is_admin), + "user": bool(self.is_user), "apikey": self._apikey, "settings": self._settings } @@ -444,17 +503,25 @@ class User(UserMixin): def get_name(self): return self._username + @property + def is_anonymous(self): + return FlaskLoginMethodReplacedByBooleanProperty("is_anonymous", lambda: False) + + @property + def is_authenticated(self): + return FlaskLoginMethodReplacedByBooleanProperty("is_authenticated", lambda: True) + @property def is_active(self): - return self._active + return FlaskLoginMethodReplacedByBooleanProperty("is_active", lambda: self._active) @property def is_user(self): - return "user" in self._roles + return OctoPrintUserMethodReplacedByBooleanProperty("is_user", lambda: "user" in self._roles) @property def is_admin(self): - return "admin" in self._roles + return OctoPrintUserMethodReplacedByBooleanProperty("is_admin", lambda: "admin" in self._roles) def get_all_settings(self): return self._settings @@ -505,6 +572,22 @@ class User(UserMixin): def __repr__(self): return "User(id=%s,name=%s,active=%r,user=%r,admin=%r)" % (self.get_id(), self.get_name(), self.is_active, self.is_user, self.is_admin) + +class AnonymousUser(AnonymousUserMixin): + + @property + def is_anonymous(self): + return FlaskLoginMethodReplacedByBooleanProperty("is_anonymous", lambda: True) + + @property + def is_authenticated(self): + return FlaskLoginMethodReplacedByBooleanProperty("is_authenticated", lambda: False) + + @property + def is_active(self): + return FlaskLoginMethodReplacedByBooleanProperty("is_active", lambda: False) + + class SessionUser(User): def __init__(self, user): self._user = user From dbe4acd47d7189bcb27f8417d9291fb2a5bb9777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 13:18:59 +0100 Subject: [PATCH 26/33] flask.request.json => flask.request.get_json() Old property is deprecated as of Flask 0.11 --- src/octoprint/plugins/corewizard/__init__.py | 6 +++--- .../plugins/softwareupdate/__init__.py | 4 +++- src/octoprint/server/api/__init__.py | 11 ++++++---- src/octoprint/server/api/printer.py | 5 ++++- src/octoprint/server/api/printer_profiles.py | 10 ++++++++-- src/octoprint/server/api/settings.py | 5 ++++- src/octoprint/server/api/slicing.py | 10 ++++++++-- src/octoprint/server/api/system.py | 6 +++--- src/octoprint/server/api/timelapse.py | 6 +++--- src/octoprint/server/api/users.py | 20 +++++++++++++++---- src/octoprint/server/apps/__init__.py | 5 ++++- 11 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/octoprint/plugins/corewizard/__init__.py b/src/octoprint/plugins/corewizard/__init__.py index f5475239..787300b6 100644 --- a/src/octoprint/plugins/corewizard/__init__.py +++ b/src/octoprint/plugins/corewizard/__init__.py @@ -87,9 +87,9 @@ class CoreWizardPlugin(octoprint.plugin.AssetPlugin, from flask import request from octoprint.server.api import valid_boolean_trues, NO_CONTENT - data = request.values - if hasattr(request, "json") and request.json: - data = request.json + data = request.get_json() + if data is None: + data = request.values if "ac" in data and data["ac"] in valid_boolean_trues and \ "user" in data.keys() and "pass1" in data.keys() and \ diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index 61558718..fccd2214 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -495,7 +495,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, if not "application/json" in flask.request.headers["Content-Type"]: return flask.make_response("Expected content-type JSON", 400) - json_data = flask.request.json + json_data = flask.request.get_json(silent=True) + if json_data is None: + return flask.make_response("Invalid JSON", 400) if "check" in json_data: check_targets = map(lambda x: x.strip(), json_data["check"]) diff --git a/src/octoprint/server/api/__init__.py b/src/octoprint/server/api/__init__.py index 6d056fe0..ecad3e93 100644 --- a/src/octoprint/server/api/__init__.py +++ b/src/octoprint/server/api/__init__.py @@ -134,10 +134,13 @@ def wizardFinish(): data = dict() try: - data = request.json + data = request.get_json() except: abort(400) + if data is None: + abort(400) + if not "handled" in data: abort(400) handled = data["handled"] @@ -186,9 +189,9 @@ def apiVersion(): @api.route("/login", methods=["POST"]) def login(): - data = request.values - if hasattr(request, "json") and request.json: - data = request.json + data = request.get_json() + if data is None: + data = request.values if octoprint.server.userManager.enabled and "user" in data and "pass" in data: username = data["user"] diff --git a/src/octoprint/server/api/printer.py b/src/octoprint/server/api/printer.py index b6e81b22..68d69146 100644 --- a/src/octoprint/server/api/printer.py +++ b/src/octoprint/server/api/printer.py @@ -326,10 +326,13 @@ def printerCommand(): return make_response("Expected content type JSON", 400) try: - data = request.json + data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if data is None: + return make_response("Malformed JSON body in request", 400) + if "command" in data and "commands" in data: return make_response("'command' and 'commands' are mutually exclusive", 400) elif ("command" in data or "commands" in data) and "script" in data: diff --git a/src/octoprint/server/api/printer_profiles.py b/src/octoprint/server/api/printer_profiles.py index bc9f6f2c..6e3d5dcd 100644 --- a/src/octoprint/server/api/printer_profiles.py +++ b/src/octoprint/server/api/printer_profiles.py @@ -50,10 +50,13 @@ def printerProfilesAdd(): return make_response("Expected content-type JSON", 400) try: - json_data = request.json + json_data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if json_data is None: + return make_response("Malformed JSON body in request", 400) + if not "profile" in json_data: return make_response("No profile included in request", 400) @@ -117,10 +120,13 @@ def printerProfilesUpdate(identifier): return make_response("Expected content-type JSON", 400) try: - json_data = request.json + json_data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if json_data is None: + return make_response("Malformed JSON body in request", 400) + if not "profile" in json_data: return make_response("No profile included in request", 400) diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py index 7ef37a35..645a4ef7 100644 --- a/src/octoprint/server/api/settings.py +++ b/src/octoprint/server/api/settings.py @@ -251,10 +251,13 @@ def setSettings(): return make_response("Expected content-type JSON", 400) try: - data = request.json + data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if data is None: + return make_response("Malformed JSON body in request", 400) + _saveSettings(data) return getSettings() diff --git a/src/octoprint/server/api/slicing.py b/src/octoprint/server/api/slicing.py index 824faf02..6644024e 100644 --- a/src/octoprint/server/api/slicing.py +++ b/src/octoprint/server/api/slicing.py @@ -118,10 +118,13 @@ def slicingAddSlicerProfile(slicer, name): return make_response("Expected content-type JSON", 400) try: - json_data = request.json + json_data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if json_data is None: + return make_response("Malformed JSON body in request", 400) + data = dict() display_name = None description = None @@ -157,10 +160,13 @@ def slicingPatchSlicerProfile(slicer, name): return make_response("Profile {name} for slicer {slicer} not found".format(**locals()), 404) try: - json_data = request.json + json_data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if json_data is None: + return make_response("Malformed JSON body in request", 400) + data = dict() display_name = None description = None diff --git a/src/octoprint/server/api/system.py b/src/octoprint/server/api/system.py index 14a0e084..24b0ae52 100644 --- a/src/octoprint/server/api/system.py +++ b/src/octoprint/server/api/system.py @@ -24,9 +24,9 @@ from octoprint.server.util.flask import restricted_access, get_remote_address def performSystemAction(): logging.getLogger(__name__).warn("Deprecated API call to /api/system made by {}, should be migrated to use /system/commands/custom/".format(get_remote_address(request))) - data = request.values - if hasattr(request, "json") and request.json: - data = request.json + data = request.get_json(silent=True) + if data is None: + data = request.values if not "action" in data: return make_response("action to perform is not defined", 400) diff --git a/src/octoprint/server/api/timelapse.py b/src/octoprint/server/api/timelapse.py index e7272eb6..a108fad4 100644 --- a/src/octoprint/server/api/timelapse.py +++ b/src/octoprint/server/api/timelapse.py @@ -162,9 +162,9 @@ def processUnrenderedTimelapseCommand(name): @api.route("/timelapse", methods=["POST"]) @restricted_access def setTimelapseConfig(): - data = request.values - if hasattr(request, "json") and request.json: - data = request.json + data = request.get_json(silent=True) + if data is None: + data = request.values if "type" in data: config = { diff --git a/src/octoprint/server/api/users.py b/src/octoprint/server/api/users.py index fbee5c16..b907e560 100644 --- a/src/octoprint/server/api/users.py +++ b/src/octoprint/server/api/users.py @@ -40,10 +40,13 @@ def addUser(): return make_response("Expected content-type JSON", 400) try: - data = request.json + data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if data is None: + return make_response("Malformed JSON body in request", 400) + if not "name" in data: return make_response("Missing mandatory name field", 400) if not "password" in data: @@ -95,10 +98,13 @@ def updateUser(username): return make_response("Expected content-type JSON", 400) try: - data = request.json + data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if data is None: + return make_response("Malformed JSON body in request", 400) + # change roles roles = ["user"] if "admin" in data and data["admin"] in valid_boolean_trues: @@ -138,10 +144,13 @@ def changePasswordForUser(username): return make_response("Expected content-type JSON", 400) try: - data = request.json + data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if data is None: + return make_response("Malformed JSON body in request", 400) + if not "password" in data or not data["password"]: return make_response("password is missing from request", 400) @@ -179,10 +188,13 @@ def changeSettingsForUser(username): return make_response("Forbidden", 403) try: - data = request.json + data = request.get_json() except BadRequest: return make_response("Malformed JSON body in request", 400) + if data is None: + return make_response("Malformed JSON body in request", 400) + try: userManager.changeUserSettings(username, data) return jsonify(SUCCESS) diff --git a/src/octoprint/server/apps/__init__.py b/src/octoprint/server/apps/__init__.py index f5e9e328..b98d11e1 100644 --- a/src/octoprint/server/apps/__init__.py +++ b/src/octoprint/server/apps/__init__.py @@ -29,7 +29,10 @@ def verifySessionKey(): if not "application/json" in request.headers["Content-Type"]: return None, None, make_response("Expected content-type JSON", 400) - data = request.json + data = request.get_json() + if data is None: + return make_response("Malformed JSON body in request", 400) + for key in ("appid", "key", "_sig"): if not key in data: return make_response("Missing argument: {key}".format(key=key), 400) From cf20b3a3c38da8effa1a99d4a843ea60d87e4769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 13:19:44 +0100 Subject: [PATCH 27/33] flask.ext.foo => flask_foo for things new on devel --- src/octoprint/server/util/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/octoprint/server/util/__init__.py b/src/octoprint/server/util/__init__.py index 5d202d16..cd95e76e 100644 --- a/src/octoprint/server/util/__init__.py +++ b/src/octoprint/server/util/__init__.py @@ -13,6 +13,8 @@ from octoprint.users import ApiUser from octoprint.util import deprecated import flask as _flask +import flask_login +import flask_principal from . import flask from . import sockjs @@ -58,9 +60,9 @@ def loginFromApiKeyRequestHandler(): if apikey and apikey != octoprint.server.UI_API_KEY and not octoprint.server.appSessionManager.validate(apikey): user = get_user_for_apikey(apikey) - if user is not None and _flask.ext.login.login_user(user, remember=False): - _flask.ext.principal.identity_changed.send(_flask.current_app._get_current_object(), - identity=_flask.ext.principal.Identity(user.get_id())) + if user is not None and not user.is_anonymous() and flask_login.login_user(user, remember=False): + flask_principal.identity_changed.send(_flask.current_app._get_current_object(), + identity=flask_principal.Identity(user.get_id())) else: return _flask.make_response("Invalid API key", 401) From 5d07c5a0b2898f836a4602dac259fe95f6d6636c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 13:21:18 +0100 Subject: [PATCH 28/33] Updated mirrored methods of custom tornado http server Should now be up to date with stock implementation again. --- src/octoprint/server/util/tornado.py | 123 ++++++++++++++------------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 8325bcaf..d25fe343 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -7,11 +7,7 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import logging import os -import datetime -import stat import mimetypes -import email -import time import re import tornado @@ -530,7 +526,8 @@ class WsgiInputContainer(object): if not data: raise Exception("WSGI app did not call start_response") - status_code = int(data["status"].split()[0]) + status_code, reason = data["status"].split(" ", 1) + status_code = int(status_code) headers = data["headers"] header_set = set(k.lower() for (k, v) in headers) body = tornado.escape.utf8(body) @@ -542,13 +539,12 @@ class WsgiInputContainer(object): if "server" not in header_set: headers.append(("Server", "TornadoServer/%s" % tornado.version)) - parts = [tornado.escape.utf8("HTTP/1.1 " + data["status"] + "\r\n")] + start_line = tornado.httputil.ResponseStartLine("HTTP/1.1", status_code, reason) + header_obj = tornado.httputil.HTTPHeaders() for key, value in headers: - parts.append(tornado.escape.utf8(key) + b": " + tornado.escape.utf8(value) + b"\r\n") - parts.append(b"\r\n") - parts.append(body) - request.write(b"".join(parts)) - request.finish() + header_obj.add(key, value) + request.connection.write_headers(start_line, header_obj, chunk=body) + request.connection.finish() self._log(status_code, request) @staticmethod @@ -583,22 +579,22 @@ class WsgiInputContainer(object): host = request.host port = 443 if request.protocol == "https" else 80 environ = { - "REQUEST_METHOD": request.method, - "SCRIPT_NAME": "", - "PATH_INFO": to_wsgi_str(tornado.escape.url_unescape( - request.path, encoding=None, plus=False)), - "QUERY_STRING": request.query, - "REMOTE_ADDR": request.remote_ip, - "SERVER_NAME": host, - "SERVER_PORT": str(port), - "SERVER_PROTOCOL": request.version, - "wsgi.version": (1, 0), - "wsgi.url_scheme": request.protocol, - "wsgi.input": request_body, - "wsgi.errors": sys.stderr, - "wsgi.multithread": False, - "wsgi.multiprocess": True, - "wsgi.run_once": False, + "REQUEST_METHOD": request.method, + "SCRIPT_NAME": "", + "PATH_INFO": to_wsgi_str(tornado.escape.url_unescape( + request.path, encoding=None, plus=False)), + "QUERY_STRING": request.query, + "REMOTE_ADDR": request.remote_ip, + "SERVER_NAME": host, + "SERVER_PORT": str(port), + "SERVER_PROTOCOL": request.version, + "wsgi.version": (1, 0), + "wsgi.url_scheme": request.protocol, + "wsgi.input": request_body, + "wsgi.errors": sys.stderr, + "wsgi.multithread": False, + "wsgi.multiprocess": True, + "wsgi.run_once": False, } if "Content-Type" in request.headers: environ["CONTENT_TYPE"] = request.headers.pop("Content-Type") @@ -619,7 +615,7 @@ class WsgiInputContainer(object): log_method = access_log.error request_time = 1000.0 * request.request_time() summary = request.method + " " + request.uri + " (" + \ - request.remote_ip + ")" + request.remote_ip + ")" log_method("%d %s %.2fms", status_code, summary, request_time) @@ -645,35 +641,21 @@ class CustomHTTPServer(tornado.httpserver.HTTPServer): def __init__(self, *args, **kwargs): pass - def initialize(self, request_callback, no_keep_alive=False, io_loop=None, - xheaders=False, ssl_options=None, protocol=None, - decompress_request=False, - chunk_size=None, max_header_size=None, - idle_connection_timeout=None, body_timeout=None, - max_body_sizes=None, default_max_body_size=None, max_buffer_size=None): - self.request_callback = request_callback - self.no_keep_alive = no_keep_alive - self.xheaders = xheaders - self.protocol = protocol - self.conn_params = CustomHTTP1ConnectionParameters( - decompress=decompress_request, - chunk_size=chunk_size, - max_header_size=max_header_size, - header_timeout=idle_connection_timeout or 3600, - max_body_sizes=max_body_sizes, - default_max_body_size=default_max_body_size, - body_timeout=body_timeout) - tornado.tcpserver.TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options, - max_buffer_size=max_buffer_size, - read_chunk_size=chunk_size) - self._connections = set() + def initialize(self, *args, **kwargs): + default_max_body_size = kwargs.pop("default_max_body_size", None) + max_body_sizes = kwargs.pop("max_body_sizes", None) + + tornado.httpserver.HTTPServer.initialize(self, *args, **kwargs) + + additional = dict(default_max_body_size=default_max_body_size, + max_body_sizes=max_body_sizes) + self.conn_params = CustomHTTP1ConnectionParameters.from_stock_params(self.conn_params, **additional) def handle_stream(self, stream, address): context = tornado.httpserver._HTTPRequestContext(stream, address, - self.protocol) - conn = CustomHTTP1ServerConnection( - stream, self.conn_params, context) + self.protocol) + conn = CustomHTTP1ServerConnection(stream, self.conn_params, context) self._connections.add(conn) conn.start_serving(self) @@ -690,7 +672,7 @@ class CustomHTTP1ServerConnection(tornado.http1connection.HTTP1ServerConnection) try: while True: conn = CustomHTTP1Connection(self.stream, False, - self.params, self.context) + self.params, self.context) request_delegate = delegate.start_request(self, conn) try: ret = yield conn.read_response(request_delegate) @@ -734,8 +716,13 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection): current request exceeds the individual max content length, the request processing is aborted and an ``HTTPInputError`` is raised. """ - content_length = headers.get("Content-Length") if "Content-Length" in headers: + if "Transfer-Encoding" in headers: + # Response cannot contain both Content-Length and + # Transfer-Encoding headers. + # http://tools.ietf.org/html/rfc7230#section-3.3.3 + raise tornado.httputil.HTTPInputError( + "Response with both Transfer-Encoding and Content-Length") if "," in headers["Content-Length"]: # Proxies sometimes cause Content-Length headers to get # duplicated. If all the values are identical then we can @@ -746,9 +733,14 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection): "Multiple unequal Content-Lengths: %r" % headers["Content-Length"]) headers["Content-Length"] = pieces[0] - content_length = int(headers["Content-Length"]) - content_length = int(content_length) + try: + content_length = int(headers["Content-Length"]) + except ValueError: + # Handles non-integer Content-Length value. + raise tornado.httputil.HTTPInputError( + "Only integer Content-Length is allowed: %s" % headers["Content-Length"]) + max_content_length = self._get_max_content_length(self._request_start_line.method, self._request_start_line.path) if max_content_length is not None and 0 <= max_content_length < content_length: raise tornado.httputil.HTTPInputError("Content-Length too long") @@ -800,9 +792,20 @@ class CustomHTTP1ConnectionParameters(tornado.http1connection.HTTP1ConnectionPar """ def __init__(self, *args, **kwargs): - tornado.http1connection.HTTP1ConnectionParameters.__init__(self, args, kwargs) - self.max_body_sizes = kwargs["max_body_sizes"] if "max_body_sizes" in kwargs else list() - self.default_max_body_size = kwargs["default_max_body_size"] if "default_max_body_size" in kwargs else None + max_body_sizes = kwargs.pop("max_body_sizes", list()) + default_max_body_size = kwargs.pop("default_max_body_size", None) + + tornado.http1connection.HTTP1ConnectionParameters.__init__(self, *args, **kwargs) + + self.max_body_sizes = max_body_sizes + self.default_max_body_size = default_max_body_size + + @classmethod + def from_stock_params(cls, other, **additional): + kwargs = dict(other.__dict__) + for key, value in additional.items(): + kwargs[key] = value + return cls(**kwargs) #~~ customized large response handler From 0cd4fc86556586747f8eab3658570a29686bef19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 13:21:42 +0100 Subject: [PATCH 29/33] Enable template auto reload by default We have our own cache, and that would get confused by not allowing a reload of the templates at runtime, which Flask 0.11+ will not allow anymore unless this setting is set to True. --- src/octoprint/server/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index a574776b..4f6f2cc3 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -617,6 +617,7 @@ class Server(object): s = settings() app.debug = self._debug + app.config["TEMPLATES_AUTO_RELOAD"] = True secret_key = s.get(["server", "secretKey"]) if not secret_key: From 9884c52c4e1831bcb3160b357c7e0908d0d9ba40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 13:23:52 +0100 Subject: [PATCH 30/33] Upgrade to current versions again --- setup.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index d2c98c6a..5cf2ad46 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ import octoprint_setuptools # Requirements for our application INSTALL_REQUIRES = [ - "flask>=0.11,<0.12", + "flask>=0.12,<0.13", "werkzeug>=0.11.1,<0.12", "tornado>=4.4.2,<4.5", "Jinja2>=2.8,<2.9", # Jinja 2.9 has breaking changes WRT template scope - we can't @@ -22,23 +22,23 @@ INSTALL_REQUIRES = [ # version, hence we need to pin to a lower version for now. See #1697 "sockjs-tornado>=1.0.2,<1.1", "PyYAML>=3.12,<3.13", - "Flask-Login>=0.3,<0.4", + "Flask-Login>=0.4,<0.5", "Flask-Principal>=0.4,<0.5", "Flask-Babel>=0.11,<0.12", "Flask-Assets>=0.12,<0.13", "markdown>=2.6.4,<2.7", - "pyserial>=3.1.1,<3.2", + "pyserial>=3.2.1,<3.3", "netaddr>=0.7.17,<0.8", "watchdog>=0.8.3,<0.9", "sarge>=0.1.4,<0.2", "netifaces>=0.10,<0.11", "pylru>=1.0.9,<1.1", "rsa>=3.4,<3.5", - "pkginfo>=1.3.2,<1.4", - "requests>=2.11.1,<2.12", + "pkginfo>=1.4.1,<1.5", + "requests>=2.13.0,<2.14", "semantic_version>=2.6.0,<2.7", - "psutil>=4.3.1,<4.4", - "Click>=6.6,<6.7", + "psutil>=5.1.3,<5.2", + "Click>=6.7,<6.8", "awesome-slugify>=1.6.5,<1.7", "feedparser>=5.2.1,<5.3", "chainmap>=1.0.2,<1.1", @@ -55,7 +55,7 @@ EXTRA_REQUIRES = dict( # Dependencies for developing OctoPrint develop=[ # Testing dependencies - "mock>=1.0.1,<1.1", + "mock>=2.0,<2.1", "nose>=1.3.0,<1.4", "ddt", @@ -70,7 +70,7 @@ EXTRA_REQUIRES = dict( # Dependencies for developing OctoPrint plugins plugins=[ - "cookiecutter>=1.4,<1.5" + "cookiecutter>=1.5,<1.6" ] ) From 49583533355a947e788b3ce2381254c98e4c3e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 14:00:50 +0100 Subject: [PATCH 31/33] Fix repr for User and SessionUser --- src/octoprint/users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/octoprint/users.py b/src/octoprint/users.py index fb3fc83d..47a2d28a 100644 --- a/src/octoprint/users.py +++ b/src/octoprint/users.py @@ -570,7 +570,7 @@ class User(UserMixin): return True def __repr__(self): - return "User(id=%s,name=%s,active=%r,user=%r,admin=%r)" % (self.get_id(), self.get_name(), self.is_active, self.is_user, self.is_admin) + return "User(id=%s,name=%s,active=%r,user=%r,admin=%r)" % (self.get_id(), self.get_name(), bool(self.is_active), bool(self.is_user), bool(self.is_admin)) class AnonymousUser(AnonymousUserMixin): @@ -616,7 +616,7 @@ class SessionUser(User): return self._session def __repr__(self): - return "SessionUser(id=%s,name=%s,active=%r,user=%r,admin=%r,session=%s,created=%s)" % (self.get_id(), self.get_name(), self.is_active, self.is_user, self.is_admin, self._session, self._created) + return "SessionUser(id=%s,name=%s,active=%r,user=%r,admin=%r,session=%s,created=%s)" % (self.get_id(), self.get_name(), bool(self.is_active), bool(self.is_user), bool(self.is_admin), self._session, self._created) ##~~ DummyUser object to use when accessControl is disabled From 814b02900bf286d4a4fb3636851c17c177a71071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 21 Feb 2017 16:39:30 +0100 Subject: [PATCH 32/33] Fixed a test left-over Thanks for the heads-up @kevans91 --- src/octoprint/server/util/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/octoprint/server/util/__init__.py b/src/octoprint/server/util/__init__.py index cd95e76e..c04e21d8 100644 --- a/src/octoprint/server/util/__init__.py +++ b/src/octoprint/server/util/__init__.py @@ -60,7 +60,7 @@ def loginFromApiKeyRequestHandler(): if apikey and apikey != octoprint.server.UI_API_KEY and not octoprint.server.appSessionManager.validate(apikey): user = get_user_for_apikey(apikey) - if user is not None and not user.is_anonymous() and flask_login.login_user(user, remember=False): + if user is not None and not user.is_anonymous and flask_login.login_user(user, remember=False): flask_principal.identity_changed.send(_flask.current_app._get_current_object(), identity=flask_principal.Identity(user.get_id())) else: From 72da1fb0e70a7beed3514337e3ea763187e8624e Mon Sep 17 00:00:00 2001 From: Andy Werner Date: Fri, 28 Apr 2017 19:30:10 +0200 Subject: [PATCH 33/33] Software update plugin: added check_type bitbucket_commit --- .../plugins/softwareupdate/__init__.py | 8 +-- .../softwareupdate/version_checks/__init__.py | 4 +- .../version_checks/bitbucket_commit.py | 54 +++++++++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/octoprint/plugins/softwareupdate/version_checks/bitbucket_commit.py diff --git a/src/octoprint/plugins/softwareupdate/__init__.py b/src/octoprint/plugins/softwareupdate/__init__.py index abe4038e..5bac02c3 100644 --- a/src/octoprint/plugins/softwareupdate/__init__.py +++ b/src/octoprint/plugins/softwareupdate/__init__.py @@ -827,7 +827,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, self._settings.load() # persist the new version if necessary for check type - if check["type"] == "github_commit": + if check["type"] in ["github_commit", "bitbucket_commit"]: dummy_default = dict(plugins=dict()) dummy_default["plugins"][self._identifier] = dict(checks=dict()) dummy_default["plugins"][self._identifier]["checks"][target] = dict(current=None) @@ -884,7 +884,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, release_branches += [x["branch"] for x in check["prerelease_branches"]] result["released_version"] = not release_branches or BRANCH in release_branches - if check["type"] == "github_commit": + if check["type"] in ["github_commit", "bitbucket_commit"]: result["current"] = REVISION if REVISION else "unknown" else: result["current"] = VERSION @@ -938,7 +938,7 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, # displayVersion AND current missing or None result["displayVersion"] = u"unknown" - if check["type"] in ("github_commit",): + if check["type"] in ["github_commit", "bitbucket_commit"]: result["current"] = check.get("current", None) else: result["current"] = check.get("current", check.get("displayVersion", None)) @@ -974,6 +974,8 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin, return version_checks.github_release elif check_type == "github_commit": return version_checks.github_commit + elif check_type == "bitbucket_commit": + return version_checks.bitbucket_commit elif check_type == "git_commit": return version_checks.git_commit elif check_type == "commandline": diff --git a/src/octoprint/plugins/softwareupdate/version_checks/__init__.py b/src/octoprint/plugins/softwareupdate/version_checks/__init__.py index 2597bd1f..fb9784f5 100644 --- a/src/octoprint/plugins/softwareupdate/version_checks/__init__.py +++ b/src/octoprint/plugins/softwareupdate/version_checks/__init__.py @@ -5,7 +5,7 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" -from . import commandline, git_commit, github_commit, github_release, python_checker +from . import commandline, git_commit, github_commit, github_release, bitbucket_commit, python_checker def log_github_ratelimit(logger, r): ratelimit = r.headers["X-RateLimit-Limit"] if "X-RateLimit-Limit" in r.headers else "?" @@ -17,4 +17,4 @@ def log_github_ratelimit(logger, r): except: reset = "?" - logger.debug("Github rate limit: %s/%s, reset at %s" % (remaining, ratelimit, reset)) \ No newline at end of file + logger.debug("Github rate limit: %s/%s, reset at %s" % (remaining, ratelimit, reset)) diff --git a/src/octoprint/plugins/softwareupdate/version_checks/bitbucket_commit.py b/src/octoprint/plugins/softwareupdate/version_checks/bitbucket_commit.py new file mode 100644 index 00000000..e4487752 --- /dev/null +++ b/src/octoprint/plugins/softwareupdate/version_checks/bitbucket_commit.py @@ -0,0 +1,54 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" + +import requests +import logging + +from ..exceptions import ConfigurationInvalid + +BRANCH_HEAD_URL = "https://api.bitbucket.org/2.0/repositories/{user}/{repo}/commit/{branch}" + +logger = logging.getLogger("octoprint.plugins.softwareupdate.version_checks.bitbucket_commit") + +def _get_latest_commit(user, repo, branch): + result = None + r = requests.get(BRANCH_HEAD_URL.format(user=user, repo=repo, branch=branch)) + + if not r.status_code == requests.codes.ok: + return None + + reference = r.json() + if not "hash" in reference: + return None + + return reference["hash"] + + +def get_latest(target, check): + if "user" not in check or "repo" not in check: + raise ConfigurationInvalid("Update configuration for %s of type bitbucket_commit needs all of user and repo" % target) + + branch = "master" + if "branch" in check: + branch = check["branch"] + + current = None + if "current" in check: + current = check["current"] + + remote_commit = _get_latest_commit(check["user"], check["repo"], branch) + + information = dict( + local=dict(name="Commit {commit}".format(commit=current if current is not None else "unknown"), value=current), + remote=dict(name="Commit {commit}".format(commit=remote_commit if remote_commit is not None else "unknown"), value=remote_commit) + ) + is_current = (current is not None and current == remote_commit) or remote_commit is None + + logger.debug("Target: %s, local: %s, remote: %s" % (target, current, remote_commit)) + + return information, is_current +