From 020e9a6a5ad7ff6a88fef986ac0e29f53d5073d4 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Thu, 13 May 2021 08:11:29 +0000 Subject: [PATCH 1/9] Update chat_mode.py --- cli/onionshare_cli/web/chat_mode.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py index 8b2a5673..0ff727c7 100644 --- a/cli/onionshare_cli/web/chat_mode.py +++ b/cli/onionshare_cli/web/chat_mode.py @@ -62,15 +62,12 @@ class ChatModeWeb: ) self.web.add_request(self.web.REQUEST_LOAD, request.path) - r = make_response( - render_template( - "chat.html", - static_url_path=self.web.static_url_path, - username=session.get("name"), - title=self.web.settings.get("general", "title"), - ) + return render_template( + "chat.html", + static_url_path=self.web.static_url_path, + username=session.get("name"), + title=self.web.settings.get("general", "title") ) - return self.web.add_security_headers(r) @self.web.app.route("/update-session-username", methods=["POST"]) def update_session_username(): @@ -87,13 +84,7 @@ class ChatModeWeb: ) self.web.add_request(self.web.REQUEST_LOAD, request.path) - r = make_response( - jsonify( - username=session.get("name"), - success=True, - ) - ) - return self.web.add_security_headers(r) + return jsonify(username=session.get("name"), success=True) @self.web.socketio.on("joined", namespace="/chat") def joined(message): From 986a9a09a91c956d55f7880f65154d3f5cb96f66 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Thu, 13 May 2021 08:13:43 +0000 Subject: [PATCH 2/9] Update receive_mode.py --- cli/onionshare_cli/web/receive_mode.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/cli/onionshare_cli/web/receive_mode.py b/cli/onionshare_cli/web/receive_mode.py index f5aae296..7d2e615c 100644 --- a/cli/onionshare_cli/web/receive_mode.py +++ b/cli/onionshare_cli/web/receive_mode.py @@ -82,16 +82,13 @@ class ReceiveModeWeb: ) self.web.add_request(self.web.REQUEST_LOAD, request.path) - r = make_response( - render_template( - "receive.html", - static_url_path=self.web.static_url_path, - disable_text=self.web.settings.get("receive", "disable_text"), - disable_files=self.web.settings.get("receive", "disable_files"), - title=self.web.settings.get("general", "title"), - ) + return render_template( + "receive.html", + static_url_path=self.web.static_url_path, + disable_text=self.web.settings.get("receive", "disable_text"), + disable_files=self.web.settings.get("receive", "disable_files"), + title=self.web.settings.get("general", "title") ) - return self.web.add_security_headers(r) @self.web.app.route("/upload", methods=["POST"]) def upload(ajax=False): @@ -218,12 +215,11 @@ class ReceiveModeWeb: ) else: # It was the last upload and the timer ran out - r = make_response( + return make_response( render_template("thankyou.html"), static_url_path=self.web.static_url_path, title=self.web.settings.get("general", "title"), ) - return self.web.add_security_headers(r) @self.web.app.route("/upload-ajax", methods=["POST"]) def upload_ajax_public(): From c19dc4fa785dfde507e48efb0cc6dd6e823d5928 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Thu, 13 May 2021 08:14:33 +0000 Subject: [PATCH 3/9] Update send_base_mode.py --- cli/onionshare_cli/web/send_base_mode.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/onionshare_cli/web/send_base_mode.py b/cli/onionshare_cli/web/send_base_mode.py index 742f6f75..d269dc69 100644 --- a/cli/onionshare_cli/web/send_base_mode.py +++ b/cli/onionshare_cli/web/send_base_mode.py @@ -145,10 +145,9 @@ class SendBaseModeWeb: # If filesystem_path is None, this is the root directory listing files, dirs = self.build_directory_listing(path, filenames, filesystem_path) - r = self.directory_listing_template( + return self.directory_listing_template( path, files, dirs, breadcrumbs, breadcrumbs_leaf ) - return self.web.add_security_headers(r) def build_directory_listing(self, path, filenames, filesystem_path): files = [] @@ -286,7 +285,6 @@ class SendBaseModeWeb: "filename*": "UTF-8''%s" % url_quote(basename), } r.headers.set("Content-Disposition", "inline", **filename_dict) - r = self.web.add_security_headers(r) (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: r.headers.set("Content-Type", content_type) From 04fae8ada11da6b03cc84ff1bbd6b34045aa9720 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Thu, 13 May 2021 08:15:17 +0000 Subject: [PATCH 4/9] Update share_mode.py --- cli/onionshare_cli/web/share_mode.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cli/onionshare_cli/web/share_mode.py b/cli/onionshare_cli/web/share_mode.py index 95aec1ba..acd295ac 100644 --- a/cli/onionshare_cli/web/share_mode.py +++ b/cli/onionshare_cli/web/share_mode.py @@ -149,8 +149,7 @@ class ShareModeWeb(SendBaseModeWeb): and self.download_in_progress ) if deny_download: - r = make_response(render_template("denied.html")) - return self.web.add_security_headers(r) + return render_template("denied.html") # If download is allowed to continue, serve download page if self.should_use_gzip(): @@ -172,8 +171,7 @@ class ShareModeWeb(SendBaseModeWeb): and self.download_in_progress ) if deny_download: - r = make_response(render_template("denied.html")) - return self.web.add_security_headers(r) + return render_template("denied.html") # Prepare some variables to use inside generate() function below # which is outside of the request context @@ -232,7 +230,6 @@ class ShareModeWeb(SendBaseModeWeb): "filename*": "UTF-8''%s" % url_quote(basename), } r.headers.set("Content-Disposition", "attachment", **filename_dict) - r = self.web.add_security_headers(r) # guess content type (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: From ea72440543a6c5fbb308be21defd0b5dc5f43f81 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Thu, 13 May 2021 08:17:51 +0000 Subject: [PATCH 5/9] Update web.py --- cli/onionshare_cli/web/web.py | 49 ++++++++++++++--------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index d88a7e4e..48f40730 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -222,6 +222,21 @@ class Web: return _check_login() + @self.app.after_request + def add_security_headers(r): + """ + Add security headers to a response + """ + for header, value in self.security_headers: + r.headers.set(header, value) + # Set a CSP header unless in website mode and the user has disabled it + if not self.settings.get("website", "disable_csp") or self.mode != "website": + r.headers.set( + "Content-Security-Policy", + "default-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; img-src 'self' data:;", + ) + return r + @self.app.errorhandler(404) def not_found(e): mode = self.get_mode() @@ -267,17 +282,11 @@ class Web: "Someone has made too many wrong attempts to guess your password, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share." ) - r = make_response( - render_template("401.html", static_url_path=self.static_url_path), 401 - ) - return self.add_security_headers(r) + return render_template("401.html", static_url_path=self.static_url_path), 401 def error403(self): self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response( - render_template("403.html", static_url_path=self.static_url_path), 403 - ) - return self.add_security_headers(r) + return render_template("403.html", static_url_path=self.static_url_path), 403 def error404(self, history_id): self.add_request( @@ -287,10 +296,7 @@ class Web: ) self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response( - render_template("404.html", static_url_path=self.static_url_path), 404 - ) - return self.add_security_headers(r) + return render_template("404.html", static_url_path=self.static_url_path), 404 def error405(self, history_id): self.add_request( @@ -300,24 +306,7 @@ class Web: ) self.add_request(Web.REQUEST_OTHER, request.path) - r = make_response( - render_template("405.html", static_url_path=self.static_url_path), 405 - ) - return self.add_security_headers(r) - - def add_security_headers(self, r): - """ - Add security headers to a request - """ - for header, value in self.security_headers: - r.headers.set(header, value) - # Set a CSP header unless in website mode and the user has disabled it - if not self.settings.get("website", "disable_csp") or self.mode != "website": - r.headers.set( - "Content-Security-Policy", - "default-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; img-src 'self' data:;", - ) - return r + return render_template("405.html", static_url_path=self.static_url_path), 405 def _safe_select_jinja_autoescape(self, filename): if filename is None: From b8b7885a5225decf5ed3273a85df053df8cf4506 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Mon, 31 May 2021 12:04:24 +0000 Subject: [PATCH 6/9] resolve conflict in web.py --- cli/onionshare_cli/web/web.py | 52 +++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index 48f40730..7b02173b 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -223,7 +223,7 @@ class Web: return _check_login() @self.app.after_request - def add_security_headers(r): + def add_security_headers(self, r): """ Add security headers to a response """ @@ -244,6 +244,20 @@ class Web: mode.cur_history_id += 1 return self.error404(history_id) + @self.app.errorhandler(405) + def method_not_allowed(e): + mode = self.get_mode() + history_id = mode.cur_history_id + mode.cur_history_id += 1 + return self.error405(history_id) + + @self.app.errorhandler(500) + def method_not_allowed(e): + mode = self.get_mode() + history_id = mode.cur_history_id + mode.cur_history_id += 1 + return self.error500(history_id) + @self.app.route("//shutdown") def shutdown(password_candidate): """ @@ -289,25 +303,41 @@ class Web: return render_template("403.html", static_url_path=self.static_url_path), 403 def error404(self, history_id): - self.add_request( - self.REQUEST_INDIVIDUAL_FILE_STARTED, - request.path, - {"id": history_id, "status_code": 404}, - ) + mode = self.get_mode() + if mode.supports_file_requests: + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 404}, + ) self.add_request(Web.REQUEST_OTHER, request.path) return render_template("404.html", static_url_path=self.static_url_path), 404 def error405(self, history_id): - self.add_request( - self.REQUEST_INDIVIDUAL_FILE_STARTED, - request.path, - {"id": history_id, "status_code": 405}, - ) + mode = self.get_mode() + if mode.supports_file_requests: + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 405}, + ) self.add_request(Web.REQUEST_OTHER, request.path) return render_template("405.html", static_url_path=self.static_url_path), 405 + def error500(self, history_id): + mode = self.get_mode() + if mode.supports_file_requests: + self.add_request( + self.REQUEST_INDIVIDUAL_FILE_STARTED, + request.path, + {"id": history_id, "status_code": 500}, + ) + + self.add_request(Web.REQUEST_OTHER, request.path) + return render_template("500.html", static_url_path=self.static_url_path), 500 + def _safe_select_jinja_autoescape(self, filename): if filename is None: return True From 56dd2d0b84207f350a2bf5725755d21f3785ec62 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Mon, 31 May 2021 12:05:31 +0000 Subject: [PATCH 7/9] resolve conflict in chat_mode.py --- cli/onionshare_cli/web/chat_mode.py | 50 ++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py index 0ff727c7..c90a269d 100644 --- a/cli/onionshare_cli/web/chat_mode.py +++ b/cli/onionshare_cli/web/chat_mode.py @@ -39,6 +39,12 @@ class ChatModeWeb: # This tracks the history id self.cur_history_id = 0 + # Whether or not we can send REQUEST_INDIVIDUAL_FILE_STARTED + # and maybe other events when requests come in to this mode + # Chat mode has no concept of individual file requests that + # turn into history widgets in the GUI, so set it to False + self.supports_file_requests = False + self.define_routes() def define_routes(self): @@ -46,7 +52,7 @@ class ChatModeWeb: The web app routes for chatting """ - @self.web.app.route("/") + @self.web.app.route("/", methods=["GET"], provide_automatic_options=False) def index(): history_id = self.cur_history_id self.cur_history_id += 1 @@ -63,28 +69,48 @@ class ChatModeWeb: self.web.add_request(self.web.REQUEST_LOAD, request.path) return render_template( - "chat.html", - static_url_path=self.web.static_url_path, - username=session.get("name"), - title=self.web.settings.get("general", "title") + "chat.html", + static_url_path=self.web.static_url_path, + username=session.get("name"), + title=self.web.settings.get("general", "title"), + ) ) - @self.web.app.route("/update-session-username", methods=["POST"]) + @self.web.app.route("/update-session-username", methods=["POST"], provide_automatic_options=False) def update_session_username(): history_id = self.cur_history_id data = request.get_json() if ( data.get("username", "") and data.get("username", "") not in self.connected_users + and len(data.get("username", "")) < 128 ): session["name"] = data.get("username", session.get("name")) - self.web.add_request( - request.path, - {"id": history_id, "status_code": 200}, - ) + self.web.add_request( + request.path, + {"id": history_id, "status_code": 200}, + ) - self.web.add_request(self.web.REQUEST_LOAD, request.path) - return jsonify(username=session.get("name"), success=True) + self.web.add_request(self.web.REQUEST_LOAD, request.path) + r = make_response( + jsonify( + username=session.get("name"), + success=True, + ) + ) + else: + self.web.add_request( + request.path, + {"id": history_id, "status_code": 403}, + ) + + r = make_response( + jsonify( + username=session.get("name"), + success=False, + ) + ) + return r @self.web.socketio.on("joined", namespace="/chat") def joined(message): From 3f4f5e22ec88e2e8e933cf4f6e6577780a3bc6ca Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Mon, 31 May 2021 12:23:32 +0000 Subject: [PATCH 8/9] fix typo --- cli/onionshare_cli/web/chat_mode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/onionshare_cli/web/chat_mode.py b/cli/onionshare_cli/web/chat_mode.py index c90a269d..e92ce385 100644 --- a/cli/onionshare_cli/web/chat_mode.py +++ b/cli/onionshare_cli/web/chat_mode.py @@ -73,7 +73,6 @@ class ChatModeWeb: static_url_path=self.web.static_url_path, username=session.get("name"), title=self.web.settings.get("general", "title"), - ) ) @self.web.app.route("/update-session-username", methods=["POST"], provide_automatic_options=False) From a132cd28f5aa20668d5fc52a38f2411458712f04 Mon Sep 17 00:00:00 2001 From: whew <73732390+whew@users.noreply.github.com> Date: Mon, 31 May 2021 12:28:57 +0000 Subject: [PATCH 9/9] fix another typo... --- cli/onionshare_cli/web/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index 7b02173b..a5c26232 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -223,7 +223,7 @@ class Web: return _check_login() @self.app.after_request - def add_security_headers(self, r): + def add_security_headers(r): """ Add security headers to a response """