diff options
author | Gina Häußge <gina@octoprint.org> | 2022-09-27 18:52:49 +0300 |
---|---|---|
committer | Gina Häußge <gina@octoprint.org> | 2022-09-27 18:52:49 +0300 |
commit | c33f8c2d08b9b10d026b8c114df95d702f89b3c7 (patch) | |
tree | 54a8bff0bb4c6e5c1ddef1b7c14890fa8b6e53a2 | |
parent | c76fee7804c3d6920c10b33eabe9f2dfbd3be525 (diff) | |
parent | cf25c523ea556b92c19e113e05a49c210f9265e0 (diff) |
Merge branch 'maintenance' into devel
-rw-r--r-- | .versioneer-lookup | 6 | ||||
-rw-r--r-- | docs/api/push.rst | 2 | ||||
-rw-r--r-- | src/octoprint/access/users.py | 33 | ||||
-rw-r--r-- | src/octoprint/plugins/errortracking/__init__.py | 4 | ||||
-rw-r--r-- | src/octoprint/schema/config/server.py | 4 | ||||
-rw-r--r-- | src/octoprint/server/util/flask.py | 26 | ||||
-rw-r--r-- | src/octoprint/server/util/sockjs.py | 1 | ||||
-rw-r--r-- | src/octoprint/server/views.py | 20 | ||||
-rw-r--r-- | src/octoprint/static/js/app/client/base.js | 10 | ||||
-rw-r--r-- | src/octoprint/static/js/app/viewmodels/loginstate.js | 2 | ||||
-rw-r--r-- | src/octoprint/static/js/reverse_proxy_test/reverse_proxy_test.js | 43 | ||||
-rw-r--r-- | src/octoprint/templates/reverse_proxy_test.jinja2 | 130 |
12 files changed, 252 insertions, 29 deletions
diff --git a/.versioneer-lookup b/.versioneer-lookup index 561f90b8c..dbd4b7952 100644 --- a/.versioneer-lookup +++ b/.versioneer-lookup @@ -24,10 +24,10 @@ maintenance 1.9.0 fa2c4680cca0d1d6b46e6c1ed68ceea4d9b79afb pep440-dev fix/.* 1.9.0 fa2c4680cca0d1d6b46e6c1ed68ceea4d9b79afb pep440-dev improve/.* 1.9.0 fa2c4680cca0d1d6b46e6c1ed68ceea4d9b79afb pep440-dev -# staging/bugfix is the branch for preparation of the 1.8.4 bugfix release +# staging/bugfix is the branch for preparation of the 1.8.5 bugfix release # so are any bug/... branches -staging/bugfix 1.8.4 d0d226f3e0ad0a6436e4f32519bf38a82aa576ca pep440-dev -bug/.* 1.8.4 d0d226f3e0ad0a6436e4f32519bf38a82aa576ca pep440-dev +staging/bugfix 1.8.5 27a576e352a9b3d9abee89ab92cd02012c0e39dd pep440-dev +bug/.* 1.8.5 27a576e352a9b3d9abee89ab92cd02012c0e39dd pep440-dev # staging/maintenance is currently the branch for preparation of 1.8.0rc6 # so is regressionfix/... diff --git a/docs/api/push.rst b/docs/api/push.rst index a9796b663..158fb3984 100644 --- a/docs/api/push.rst +++ b/docs/api/push.rst @@ -30,7 +30,7 @@ following message types are currently available for usage by 3rd party clients: * ``connected``: Initial connection information, sent only right after establishing the socket connection. See :ref:`the payload data model <sec-api-push-datamodel-connected>`. * ``reauthRequired``: A reauthentication of the current login session is required. The ``reason`` parameter in the - payload defines whether a full active login is necessary (values ``logout`` and ``removed``) or a simple passive + payload defines whether a full active login is necessary (values ``logout``, ``stale``, ``removed``) or a simple passive login will suffice (all other values). * ``current``: Rate limited general state update, payload contains information about the printer's state, job progress, accumulated temperature points and log lines since last update. OctoPrint will send these updates when new information diff --git a/src/octoprint/access/users.py b/src/octoprint/access/users.py index ec7c424fd..f3e96ee09 100644 --- a/src/octoprint/access/users.py +++ b/src/octoprint/access/users.py @@ -10,6 +10,7 @@ import uuid import wrapt from flask_login import AnonymousUserMixin, UserMixin +from passlib.hash import pbkdf2_sha256 from werkzeug.local import LocalProxy from octoprint.access.groups import Group, GroupChangeListener @@ -19,10 +20,22 @@ from octoprint.util import atomic_write, deprecated, generate_api_key from octoprint.util import get_fully_qualified_classname as fqcn from octoprint.util import to_bytes, yaml +password_hashers = [] + try: - from passlib.hash import argon2 as passwordhash -except ImportError: - from passlib.hash import pbkdf2_sha256 as passwordhash + from passlib.hash import argon2 + + # test if we can actually hash and verify, if not we won't use this backend + hash = argon2.hash("test") + argon2.verify("test", hash) + + password_hashers.append(argon2) +except Exception: + logging.getLogger(__name__).warning( + "Argon2 passlib backend is not available, not using it for password hashing" + ) + +password_hashers.append(pbkdf2_sha256) class UserManager(GroupChangeListener): @@ -143,7 +156,7 @@ class UserManager(GroupChangeListener): @staticmethod def create_password_hash(password, *args, **kwargs): - return passwordhash.hash(password) + return password_hashers[0].hash(password) @staticmethod def create_legacy_password_hash(password, salt): @@ -1170,10 +1183,14 @@ class User(UserMixin): if legacy: return self._passwordHash == password - try: - return passwordhash.verify(password, self._passwordHash) - except ValueError: - return False + for password_hash in password_hashers: + if password_hash.identify(self._passwordHash): + try: + return password_hash.verify(password, self._passwordHash) + except ValueError: + pass + + return False def get_id(self): return self.get_name() diff --git a/src/octoprint/plugins/errortracking/__init__.py b/src/octoprint/plugins/errortracking/__init__.py index 0c1ab7565..a9c4295b7 100644 --- a/src/octoprint/plugins/errortracking/__init__.py +++ b/src/octoprint/plugins/errortracking/__init__.py @@ -18,10 +18,10 @@ from octoprint.util.version import ( ) SENTRY_URL_SERVER = ( - "https://af30d06358144fb8af076ffd8984136d@o118517.ingest.sentry.io/1373987" + "https://289cde39ce7d410f82280cb5b0bb62e8@o118517.ingest.sentry.io/1373987" ) SENTRY_URL_COREUI = ( - "https://e1d6b5d760f241408382d62a3e7fb416@o118517.ingest.sentry.io/1374096" + "https://af2c0c034c9044889b8cf06163ea00fc@o118517.ingest.sentry.io/1374096" ) SETTINGS_DEFAULTS = { diff --git a/src/octoprint/schema/config/server.py b/src/octoprint/schema/config/server.py index 4a575abb0..4ac86204b 100644 --- a/src/octoprint/schema/config/server.py +++ b/src/octoprint/schema/config/server.py @@ -141,8 +141,8 @@ class CookiesConfig(BaseModel): secure: bool = False """Whether to set the `Secure` flag to true on cookies. Only set to true if you are running OctoPrint behind a reverse proxy taking care of SSL termination.""" - samesite: Optional[SameSiteEnum] = "Strict" - """`SameSite` setting to use on the cookies. Possible values are `None`, `Lax` and `Strict`. Defaults to `Strict`. Be advised that if forced unset, this has security implications as many browsers now default to `Lax` unless you configure cookies to be set with `Secure` flag set, explicitly set `SameSite` setting here and also serve OctoPrint over https. The `Lax` setting is known to cause with embedding OctoPrint in frames. See also ["Feature: Cookies default to SameSite=Lax"](https://www.chromestatus.com/feature/5088147346030592), ["Feature: Reject insecure SameSite=None cookies"](https://www.chromestatus.com/feature/5633521622188032) and [issue #3482](https://github.com/OctoPrint/OctoPrint/issues/3482).""" + samesite: Optional[SameSiteEnum] = SameSiteEnum.lax + """`SameSite` setting to use on the cookies. Possible values are `None`, `Lax` and `Strict`. Defaults to `Lax`. Be advised that if forced unset, this has security implications as many browsers now default to `Lax` unless you configure cookies to be set with `Secure` flag set, explicitly set `SameSite` setting here and also serve OctoPrint over https. The `Lax` setting is known to cause with embedding OctoPrint in frames. See also ["Feature: Cookies default to SameSite=Lax"](https://www.chromestatus.com/feature/5088147346030592), ["Feature: Reject insecure SameSite=None cookies"](https://www.chromestatus.com/feature/5633521622188032) and [issue #3482](https://github.com/OctoPrint/OctoPrint/issues/3482).""" @with_attrs_docs diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index 60cc55260..d2b41ef5f 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -505,6 +505,20 @@ def decode_remember_me_cookie(value): raise ValueError("Invalid remember me cookie") +def get_cookie_suffix(request): + """ + Request specific suffix for set and read cookies + + We need this because cookies are not port-specific and we don't want to overwrite our + session and other cookies from one OctoPrint instance on our machine with those of another + one who happens to listen on the same address albeit a different port or script root. + """ + result = "_P" + request.server_port + if request.script_root: + return result + "_R" + request.script_root.replace("/", "|") + return result + + class OctoPrintFlaskRequest(flask.Request): environment_wrapper = staticmethod(lambda x: x) @@ -553,17 +567,7 @@ class OctoPrintFlaskRequest(flask.Request): @cached_property def cookie_suffix(self): - """ - Request specific suffix for set and read cookies - - We need this because cookies are not port-specific and we don't want to overwrite our - session and other cookies from one OctoPrint instance on our machine with those of another - one who happens to listen on the same address albeit a different port or script root. - """ - result = "_P" + self.server_port - if self.script_root: - return result + "_R" + self.script_root.replace("/", "|") - return result + return get_cookie_suffix(self) class OctoPrintFlaskResponse(flask.Response): diff --git a/src/octoprint/server/util/sockjs.py b/src/octoprint/server/util/sockjs.py index 0bc529f56..f6aa9a971 100644 --- a/src/octoprint/server/util/sockjs.py +++ b/src/octoprint/server/util/sockjs.py @@ -305,6 +305,7 @@ class PrinterStateConnection( f"Unknown user/session combo: {user_id}:{user_session}" ) self._on_logout() + self._sendReauthRequired("stale") self._register() diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index 5c208105f..d836c3f38 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -331,6 +331,26 @@ def in_cache(): return abort(404) +@app.route("/reverse_proxy_test") +@app.route("/reverse_proxy_test/") +def reverse_proxy_test(): + from octoprint.server.util.flask import get_cookie_suffix, get_remote_address + + remote_address = get_remote_address(request) + cookie_suffix = get_cookie_suffix(request) + + return render_template( + "reverse_proxy_test.jinja2", + theming=[], + client_ip=remote_address, + server_protocol=request.environ.get("wsgi.url_scheme"), + server_name=request.environ.get("SERVER_NAME"), + server_port=request.environ.get("SERVER_PORT"), + server_path=request.script_root if request.script_root else "/", + cookie_suffix=cookie_suffix, + ) + + @app.route("/") def index(): from octoprint.server import connectivityChecker, printer diff --git a/src/octoprint/static/js/app/client/base.js b/src/octoprint/static/js/app/client/base.js index 1cbf2e871..79870fd86 100644 --- a/src/octoprint/static/js/app/client/base.js +++ b/src/octoprint/static/js/app/client/base.js @@ -107,7 +107,7 @@ return url; }; - OctoPrintClient.prototype.getCookieSuffix = function () { + OctoPrintClient.prototype.getParsedBaseUrl = function () { if (!this.options.baseurl) return ""; try { @@ -121,6 +121,14 @@ var url = new URL(parsed.protocol + "//" + parsed.host + path); } + return url; + }; + + OctoPrintClient.prototype.getCookieSuffix = function () { + if (!this.options.baseurl) return ""; + + var url = this.getParsedBaseUrl(); + var port = url.port || (url.protocol === "https:" ? 443 : 80); if (url.pathname && url.pathname !== "/") { var path = url.pathname; diff --git a/src/octoprint/static/js/app/viewmodels/loginstate.js b/src/octoprint/static/js/app/viewmodels/loginstate.js index e78f71cc2..6bfb286fb 100644 --- a/src/octoprint/static/js/app/viewmodels/loginstate.js +++ b/src/octoprint/static/js/app/viewmodels/loginstate.js @@ -308,7 +308,7 @@ $(function () { }; self.onDataUpdaterReauthRequired = function (reason) { - if (reason === "logout" || reason === "removed") { + if (reason === "logout" || reason === "stale" || reason === "removed") { self.logout(); } else { self.requestData(); diff --git a/src/octoprint/static/js/reverse_proxy_test/reverse_proxy_test.js b/src/octoprint/static/js/reverse_proxy_test/reverse_proxy_test.js new file mode 100644 index 000000000..fcb991d3d --- /dev/null +++ b/src/octoprint/static/js/reverse_proxy_test/reverse_proxy_test.js @@ -0,0 +1,43 @@ +$(function () { + //~~ OctoPrint client setup + + var OctoPrint = window.OctoPrint; + OctoPrint.options.baseurl = BASE_URL; + + //~~ Lodash setup + + _.mixin({sprintf: sprintf, vsprintf: vsprintf}); + + //~~ View Model + + function ReverseProxyTestViewModel() { + var self = this; + + var url = OctoPrint.getParsedBaseUrl(); + var cookieSuffix = OctoPrint.getCookieSuffix(); + var protocol = url.protocol; + var path = url.pathname || "/"; + + if (path && path !== "/") { + if (path.endsWith("/")) { + path = path.substring(0, path.length - 1); + } + } + + self.serverProtocol = protocol.substring(0, protocol.length - 1); + self.serverName = url.hostname; + self.serverPort = url.port || (url.protocol === "https:" ? 443 : 80); + self.serverPath = path; + self.cookieSuffix = cookieSuffix; + + self.serverProtocolMatch = self.serverProtocol == SERVER_PROTOCOL; + self.serverNameMatch = self.serverName == SERVER_NAME; + self.serverPortMatch = self.serverPort == SERVER_PORT; + self.serverPathMatch = self.serverPath == SERVER_PATH; + self.cookieSuffixMatch = self.cookieSuffix == COOKIE_SUFFIX; + } + + var viewModel = new ReverseProxyTestViewModel(); + + ko.applyBindings(viewModel, document.getElementById("reverse_proxy_test")); +}); diff --git a/src/octoprint/templates/reverse_proxy_test.jinja2 b/src/octoprint/templates/reverse_proxy_test.jinja2 new file mode 100644 index 000000000..801038ec8 --- /dev/null +++ b/src/octoprint/templates/reverse_proxy_test.jinja2 @@ -0,0 +1,130 @@ +<html> +<head> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <title>OctoPrint Reverse Proxy Test</title> + + <link rel="shortcut icon" href="{{ url_for('static', filename='img/tentacle-32x32.png') }}"> + <link rel="mask-icon" href="{{ url_for('static', filename='img/mask.svg') }}" color="#56BE37"> + <link rel="mask-icon-theme" href="{{ url_for('static', filename='img/mask-theme.svg') }}" color="#56BE37"> + <link rel="apple-touch-icon" sizes="114x114" href="{{ url_for('static', filename='img/apple-touch-icon-114x114.png') }}"> + <link rel="apple-touch-icon" sizes="144x144" href="{{ url_for('static', filename='img/apple-touch-icon-144x144.png') }}"> + + <!-- le CSS --> + + <link rel="stylesheet" href="{{ url_for("static", filename="css/bootstrap.min.css") }}"> + <link rel="stylesheet" href="{{ url_for("static", filename="css/bootstrap-responsive.min.css") }}"> + <link rel="stylesheet" href="{{ url_for("static", filename="vendor/font-awesome-5.15.1/css/all.min.css") }}"> + <link rel="stylesheet" href="{{ url_for("static", filename="vendor/font-awesome-5.15.1/css/v4-shims.min") }}"> + + {% for url in theming %} + <link rel="stylesheet" href="{{ url }}"> + {% endfor %} + + <!-- le javascript --> + + <script> + var BASE_URL = "{{ url_for('index') }}"; + + var CLIENT_IP = "{{ client_ip }}"; + var SERVER_PROTOCOL = "{{ server_protocol }}"; + var SERVER_NAME = "{{ server_name }}"; + var SERVER_PORT = {{ server_port }}; + var SERVER_PATH = "{{ server_path }}"; + var COOKIE_SUFFIX = "{{ cookie_suffix }}"; + </script> + <script src="{{ url_for("static", filename="js/lib/jquery/jquery.min.js") }}"></script> + <script src="{{ url_for("static", filename="js/lib/knockout.js") }}"></script> + <script src="{{ url_for("static", filename="js/lib/bootstrap/bootstrap.js") }}"></script> + <script src="{{ url_for("static", filename="js/lib/lodash.min.js") }}"></script> + <script src="{{ url_for("static", filename="js/lib/sprintf.min.js") }}"></script> + + {% assets "js_client" %} + <script type="text/javascript" src="{{ ASSET_URL }}"></script> + {% endassets %} + + <script src="{{ url_for("static", filename="js/app/helpers.js") }}"></script> + <script src="{{ url_for("static", filename="js/reverse_proxy_test/reverse_proxy_test.js") }}"></script> +</head> +<body> + <div id="navbar" class="navbar navbar-inverse navbar-static-top"> + <div class="navbar-inner"> + <span class="brand">OctoPrint Reverse Proxy Test</span> + </div> + </div> + <div id="reverse_proxy_test" class="container" style="padding-top: 1em"> + <div class="alert alert-error" data-bind="visible: !cookieSuffixMatch"> + <strong>Warning!</strong> The used cookie suffix doesn't match between client and server. That means that OctoPrint's + UI won't work properly. Please check your configuration and fix it. + </div> + + <p> + This page helps you identify issues in your reverse proxy configuration. It will show you information about + some of the variables that OctoPrint uses to set cookies and generate fully qualified URLs. If you see + any mismatches highlighted in red below, that means that something is not configured properly and you + need to fix it. + </p> + + <p> + The table also shows you the client IP that your server saw you as. Make sure that this client IP is what + OctoPrint should be seeing, and not for example your reverse proxy itself. + </p> + + <p> + For each variable, information is displayed what influences the value the server sees and thus where to + start debugging in case of any observed mismatches. + </p> + + <table class="table table-bordered table-hover"> + <thead> + <th>Variable</th> + <th>Source</th> + <th>Client</th> + <th>Server</th> + </thead> + <tr> + <td>Client IP</td> + <td><code>X-Forwarded-For</code></td> + <td>-</td> + <td>{{ client_ip }}</td> + </tr> + <tr data-bind="css: { success: serverProtocolMatch, error: !serverProtocolMatch }"> + <td>Protocol</td> + <td><code>X-Forwarded-Protocol</code>, <code>X-Scheme</code> or config</td> + <td data-bind="text: serverProtocol"></td> + <td>{{ server_protocol }}</td> + </tr> + <tr data-bind="css: { success: serverNameMatch, error: !serverNameMatch }"> + <td>Host</td> + <td><code>X-Forwarded-Host</code>, <code>Host</code>, <code>X-Forwarded-Server</code> or config</td> + <td data-bind="text: serverName"></td> + <td>{{ server_name }}</td> + </tr> + <tr data-bind="css: { success: serverPortMatch, error: !serverPortMatch }"> + <td>Port</td> + <td><code>X-Forwarded-Host</code>, <code>Host</code>, <code>X-Forwarded-Port</code>, <code>X-Forwarded-Protocol</code>, <code>X-Scheme</code> or config</td> + <td data-bind="text: serverPort"></td> + <td>{{ server_port }}</td> + </tr> + <tr data-bind="css: { success: serverPathMatch, error: !serverPathMatch }"> + <td>Path</td> + <td><code>X-Script-Name</code> or config</td> + <td data-bind="text: serverPath"></td> + <td>{{ server_path }}</td> + </tr> + <tr data-bind="css: { success: cookieSuffixMatch, error: !cookieSuffixMatch }"> + <td>Cookie Suffix</td> + <td>Built from port & path</td> + <td data-bind="text: cookieSuffix"></td> + <td>{{ cookie_suffix }}</td> + </tr> + </table> + + <p> + Please refer to <a href="https://faq.octoprint.org/reverse-proxy" target="_blank" rel="noopener noreferrer">the Reverse Proxy FAQ entry</a> + for information on correct configuration and examples. + </p> + + </div> + +</body> +</html> |