diff options
17 files changed, 552 insertions, 124 deletions
diff --git a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po index 4807509..c8d8831 100644 --- a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po +++ b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: hydrilla 2.0\n" "Report-Msgid-Bugs-To: koszko@koszko.org\n" -"POT-Creation-Date: 2022-09-08 17:52+0200\n" +"POT-Creation-Date: 2022-09-09 11:30+0200\n" "PO-Revision-Date: 2022-02-12 00:00+0000\n" "Last-Translator: Wojtek Kosior <koszko@koszko.org>\n" "Language: en_US\n" @@ -190,7 +190,7 @@ msgstr "" "Attempt was made to configure Haketilo Mitmproxy addon with data " "directory path but it has already been configured." -#: src/hydrilla/proxy/addon.py:166 +#: src/hydrilla/proxy/addon.py:165 msgid "err.proxy.unknown_error_{}_try_again" msgstr "" "Haketilo experienced an error. Try again.\n" @@ -205,39 +205,43 @@ msgstr "Requested file could not be found." msgid "api.resource_not_enabled_for_access" msgstr "Requested resource is not enabled for access." -#: src/hydrilla/proxy/state_impl/concrete_state.py:106 +#: src/hydrilla/proxy/state_impl/concrete_state.py:120 msgid "err.proxy.unknown_db_schema" msgstr "" "Haketilo's data files have been altered, possibly by a newer version of " "Haketilo." -#: src/hydrilla/proxy/state_impl/concrete_state.py:110 +#: src/hydrilla/proxy/state_impl/concrete_state.py:124 msgid "err.proxy.no_sqlite_foreign_keys" msgstr "" "This installation of Haketilo uses an SQLite version which does not " "support foreign key constraints." -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:63 +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:69 msgid "web_ui.base.title.haketilo_proxy" msgstr "Haketilo" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:228 +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:238 msgid "web_ui.base.nav.home" msgstr "Home" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:229 +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:239 +msgid "web_ui.base.nav.options" +msgstr "Options" + +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:240 msgid "web_ui.base.nav.packages" msgstr "Packages" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:230 +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:241 msgid "web_ui.base.nav.libraries" msgstr "Libraries" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:231 +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:242 msgid "web_ui.base.nav.repos" msgstr "Repositories" -#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:232 +#: src/hydrilla/proxy/web_ui/templates/base.html.jinja:243 msgid "web_ui.base.nav.load" msgstr "Import from file" @@ -257,7 +261,7 @@ msgstr "" #: src/hydrilla/proxy/web_ui/templates/index.html.jinja:37 msgid "web_ui.home.heading.about_haketilo" -msgstr "About Haketilo" +msgstr "About this tool" #: src/hydrilla/proxy/web_ui/templates/index.html.jinja:41 msgid "web_ui.home.haketilo_is_blah_blah" @@ -268,6 +272,7 @@ msgstr "" "extension but has since been made into an HTTP proxy." #: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:25 +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:30 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:33 msgid "web_ui.err.file_installation_error" msgstr "Failed to install needed items from repository." @@ -277,6 +282,7 @@ msgid "web_ui.err.uninstall_disallowed" msgstr "This item is required and cannot be uninstalled." #: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:33 +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:34 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:37 msgid "web_ui.err.repo_communication_error" msgstr "Couldn't communicate with repository." @@ -515,15 +521,83 @@ msgstr "Available packages" msgid "web_ui.packages.enabled_version_{}" msgstr "enabled version {}" +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:23 +msgid "web_ui.options.title" +msgstr "Global options" + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:27 +msgid "web_ui.options.heading" +msgstr "Haketilo global options" + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:37 +msgid "web_ui.options.packages_are_used_when_enabled" +msgstr "" +"Hektilo is currently configured to only use packages that were explicitly" +" enabled." + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:40 +msgid "web_ui.options.user_gets_asked_whether_to_enable_package" +msgstr "" +"Hektilo is currently configured to ask whenever a package is found that " +"could be used for the current site." + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:44 +msgid "web_ui.options.packages_are_used_automatically" +msgstr "" +"Hektilo is currently configured to automatically use packages that are " +"available for the current site." + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:51 +msgid "web_ui.options.use_enabled_button" +msgstr "Use when enabled" + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:54 +msgid "web_ui.options.use_question_button" +msgstr "Ask whether to use" + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:57 +msgid "web_ui.options.use_auto_button" +msgstr "Use automatically" + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:70 +msgid "web_ui.options.scripts_are_allowed_by_default" +msgstr "" +"By default Haketilo currently allows JavaScript sent by websites to the " +"browser to execute." + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:73 +msgid "web_ui.options.scripts_are_blocked_by_default" +msgstr "" +"By default Haketilo currently blocks JavaScript sent by websites to the " +"browser from executing." + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:77 +msgid "web_ui.options.allow_scripts_button" +msgstr "Allow scripts" + +#: src/hydrilla/proxy/web_ui/templates/options.html.jinja:78 +msgid "web_ui.options.block_scripts_button" +msgstr "Block scripts" + #: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:24 msgid "web_ui.prompts.auto_install_error.title" msgstr "Installation failure" -#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:41 +#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:29 +msgid "web_ui.err.retry_install.file_installation_error" +msgstr "" +"Another failure occured when retrying to install needed items from " +"repository." + +#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:33 +msgid "web_ui.err.retry_install.repo_communication_error" +msgstr "Another failure occured when retrying to communicate with repository." + +#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:37 msgid "web_ui.prompts.auto_install_error.heading" msgstr "Installation failure" -#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:46 +#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:42 msgid "web_ui.prompts.auto_install_error.package_{}_failed_to_install" msgstr "" "Automatically activated package '{}' failed to install because Haketilo " @@ -531,14 +605,36 @@ msgstr "" "that you do have network connection and try again. You can also choose to" " permanently disable the package." -#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:52 +#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:47 msgid "web_ui.prompts.auto_install_error.disable_button" msgstr "Disable" -#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:57 +#: src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja:48 msgid "web_ui.prompts.auto_install_error.retry_button" msgstr "Retry installation" +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:25 +msgid "web_ui.prompts.package_suggestion.title" +msgstr "Package suggestion" + +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:38 +msgid "web_ui.prompts.package_suggestion.heading" +msgstr "Package suitable for current site was found" + +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:43 +msgid "web_ui.prompts.package_suggestion.do_you_want_to_enable_package_{}" +msgstr "" +"Do you want to enable package '{}'? It will then be used whenever you " +"visit this site." + +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:48 +msgid "web_ui.prompts.package_suggestion.disable_button" +msgstr "Disable" + +#: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:49 +msgid "web_ui.prompts.package_suggestion.enable_button" +msgstr "Enable" + #: src/hydrilla/proxy/web_ui/templates/repos/add.html.jinja:23 msgid "web_ui.repos.add.title" msgstr "New repository" @@ -660,11 +756,11 @@ msgstr "Last refreshed on {}." msgid "web_ui.repos.single.refresh_now_button" msgstr "Refresh" -#: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:162 +#: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:163 msgid "web_ui.repos.item_count_{mappings}_{resources}" msgstr "packages: {mappings}; libraries: {resources}" -#: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:173 +#: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:174 msgid "web_ui.repos.single.remove_button" msgstr "Remove repository" diff --git a/src/hydrilla/proxy/policies/base.py b/src/hydrilla/proxy/policies/base.py index bb95d29..8ffc45e 100644 --- a/src/hydrilla/proxy/policies/base.py +++ b/src/hydrilla/proxy/policies/base.py @@ -58,22 +58,21 @@ class Policy(ABC): """....""" process_request: t.ClassVar[bool] = False process_response: t.ClassVar[bool] = False + anticache: t.ClassVar[bool] = True priority: t.ClassVar[PolicyPriority] - @property - def anticache(self) -> bool: - return self.process_request or self.process_response - def consume_request(self, request_info: http_messages.RequestInfo) \ -> t.Optional[ProducedMessage]: - """....""" - return None + raise NotImplementedError( + 'This kind of policy does not consume requests.' + ) def consume_response(self, response_info: http_messages.ResponseInfo) \ -> t.Optional[http_messages.ProducedResponse]: - """....""" - return None + raise NotImplementedError( + 'This kind of policy does not consume responses.' + ) # mypy needs to be corrected: diff --git a/src/hydrilla/proxy/policies/payload.py b/src/hydrilla/proxy/policies/payload.py index a063c7c..1069c41 100644 --- a/src/hydrilla/proxy/policies/payload.py +++ b/src/hydrilla/proxy/policies/payload.py @@ -60,6 +60,20 @@ class PayloadAwarePolicy(base.Policy): return f'{request_url.url_without_path}/{"/".join(base_path_segments)}/' + def _payload_details_to_signed_query_string( + self, + _salt: str, + **extra_keys: str + ) -> str: + params: t.Mapping[str, str] = { + 'payload_id': self.payload_data.ref.id, + **extra_keys + } + + serializer = URLSafeSerializer(self.payload_data.global_secret, _salt) + + return urlencode({'details': serializer.dumps(params)}) + @dc.dataclass(frozen=True) # type: ignore[misc] class PayloadAwarePolicyFactory(base.PolicyFactory): @@ -283,20 +297,18 @@ class AutoPayloadInjectPolicy(PayloadInjectPolicy): return super().consume_response(response_info) except (state.RepoCommunicationError, state.FileInstallationError, _PayloadHasProblemsError) as ex: - params = { - 'next_url': response_info.url.orig_url, - 'payload_id': self.payload_data.ref.id + extra_params: dict[str, str] = { + 'next_url': response_info.url.orig_url } - if isinstance(ex, state.FileInstallationError): - params['repo_id'] = ex.repo_id - params['file_sha256'] = ex.sha256 + extra_params['repo_id'] = ex.repo_id + extra_params['file_sha256'] = ex.sha256 - serializer = URLSafeSerializer( - self.payload_data.global_secret, - salt = 'auto_install_error' + query = self._payload_details_to_signed_query_string( + _salt = 'auto_install_error', + **extra_params ) - query = urlencode({'details': serializer.dumps(params)}) + redirect_url = 'https://hkt.mitm.it/auto_install_error?' + query msg = 'Error occured when installing payload. Redirecting.' @@ -310,13 +322,25 @@ class AutoPayloadInjectPolicy(PayloadInjectPolicy): @dc.dataclass(frozen=True) class PayloadSuggestPolicy(PayloadAwarePolicy): """....""" + process_request: t.ClassVar[bool] = True + priority: t.ClassVar[base.PolicyPriority] = base.PolicyPriority._ONE - def make_response(self, request_info: http_messages.RequestInfo) \ + def consume_request(self, request_info: http_messages.RequestInfo) \ -> http_messages.ProducedResponse: - """....""" - # TODO: implement - return http_messages.ProducedResponse(200, ((b'a', b'b'),), b'') + query = self._payload_details_to_signed_query_string( + _salt = 'package_suggestion', + next_url = request_info.url.orig_url + ) + + redirect_url = 'https://hkt.mitm.it/package_suggestion?' + query + msg = 'A package was found that could be used on this site. Redirecting.' + + return http_messages.ProducedResponse( + status_code = 303, + headers = [(b'Location', redirect_url.encode())], + body = msg.encode() + ) @dc.dataclass(frozen=True, unsafe_hash=True) diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py index 491a865..abea7a7 100644 --- a/src/hydrilla/proxy/state.py +++ b/src/hydrilla/proxy/state.py @@ -448,11 +448,6 @@ class HaketiloState(ABC): ... @abstractmethod - def get_repo_iteration(self, repo_iteration_id: str) -> RepoIterationRef: - """....""" - ... - - @abstractmethod def mapping_store(self) -> MappingStore: ... diff --git a/src/hydrilla/proxy/state_impl/base.py b/src/hydrilla/proxy/state_impl/base.py index 0559a42..75d733f 100644 --- a/src/hydrilla/proxy/state_impl/base.py +++ b/src/hydrilla/proxy/state_impl/base.py @@ -140,10 +140,9 @@ class HaketiloStateWithFields(st.HaketiloState): """....""" store_dir: Path connection: sqlite3.Connection + settings: st.HaketiloGlobalSettings current_cursor: t.Optional[sqlite3.Cursor] = None - #settings: st.HaketiloGlobalSettings - secret: bytes = dc.field(default_factory=(lambda: secrets.token_bytes(16))) policy_tree: PolicyTree = PolicyTree() diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py index 8bd25a9..3611db1 100644 --- a/src/hydrilla/proxy/state_impl/concrete_state.py +++ b/src/hydrilla/proxy/state_impl/concrete_state.py @@ -56,10 +56,24 @@ from . import _operations here = Path(__file__).resolve().parent -@dc.dataclass(frozen=True, unsafe_hash=True) -class ConcreteRepoIterationRef(st.RepoIterationRef): - pass - +def load_settings(cursor: sqlite3.Cursor) -> st.HaketiloGlobalSettings: + cursor.execute( + ''' + SELECT + default_allow_scripts, repo_refresh_seconds, mapping_use_mode + FROM + general + ''' + ) + + (default_allow_scripts, repo_refresh_seconds, + mapping_use_mode), = cursor.fetchall() + + return st.HaketiloGlobalSettings( + default_allow_scripts = default_allow_scripts, + repo_refresh_seconds = repo_refresh_seconds, + mapping_use_mode = st.MappingUseMode(mapping_use_mode) + ) @dc.dataclass class ConcreteHaketiloState(base.HaketiloStateWithFields): @@ -233,9 +247,6 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): def repo_store(self) -> st.RepoStore: return repos.ConcreteRepoStore(self) - def get_repo_iteration(self, repo_iteration_id: str) -> st.RepoIterationRef: - return ConcreteRepoIterationRef(repo_iteration_id) - def mapping_store(self) -> st.MappingStore: return items.ConcreteMappingStore(self) @@ -255,11 +266,8 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): return self.secret def get_settings(self) -> st.HaketiloGlobalSettings: - return st.HaketiloGlobalSettings( - mapping_use_mode = st.MappingUseMode.AUTO, - default_allow_scripts = False, - repo_refresh_seconds = 0 - ) + with self.lock: + return self.settings def update_settings( self, @@ -268,7 +276,18 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): default_allow_scripts: t.Optional[bool] = None, repo_refresh_seconds: t.Optional[int] = None ) -> None: - raise NotImplementedError() + with self.cursor(transaction=True) as cursor: + def set_opt(col_name: str, val: t.Union[bool, int, str]) -> None: + cursor.execute(f'UPDATE general SET {col_name} = ?;', (val,)) + + if mapping_use_mode is not None: + set_opt('mapping_use_mode', mapping_use_mode.value) + if default_allow_scripts is not None: + set_opt('default_allow_scripts', default_allow_scripts) + if repo_refresh_seconds is not None: + set_opt('repo_refresh_seconds', repo_refresh_seconds) + + self.settings = load_settings(cursor) @staticmethod def make(store_dir: Path) -> 'ConcreteHaketiloState': @@ -277,7 +296,11 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): isolation_level = None, check_same_thread = False ) + + global_settings = load_settings(connection.cursor()) + return ConcreteHaketiloState( - store_dir = store_dir, - connection = connection + store_dir = store_dir, + connection = connection, + settings = global_settings ) diff --git a/src/hydrilla/proxy/state_impl/repos.py b/src/hydrilla/proxy/state_impl/repos.py index 8a3fe64..383d147 100644 --- a/src/hydrilla/proxy/state_impl/repos.py +++ b/src/hydrilla/proxy/state_impl/repos.py @@ -363,8 +363,3 @@ class ConcreteRepoStore(st.RepoStore): result.append(make_repo_display_info(ref, *rest)) return result - - -@dc.dataclass(frozen=True, unsafe_hash=True) -class ConcreteRepoIterationRef(st.RepoIterationRef): - state: base.HaketiloStateWithFields diff --git a/src/hydrilla/proxy/web_ui/options.py b/src/hydrilla/proxy/web_ui/options.py new file mode 100644 index 0000000..f24c356 --- /dev/null +++ b/src/hydrilla/proxy/web_ui/options.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Proxy web UI options page. +# +# This file is part of Hydrilla&Haketilo. +# +# Copyright (C) 2022 Wojtek Kosior +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +# I, Wojtek Kosior, thereby promise not to sue for violation of this +# file's license. Although I request that you do not make use this code +# in a proprietary program, I am not going to enforce this in court. + +# Enable using with Python 3.7. +from __future__ import annotations + +import typing as t + +import flask +import werkzeug + +from .. import state as st +from . import _app + + +bp = flask.Blueprint('options', __package__) + +@bp.route('/options', methods=['GET']) +def options(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: + html = flask.render_template( + 'options.html.jinja', + settings = _app.get_haketilo_state().get_settings(), + **errors + ) + return flask.make_response(html, 200) + +@bp.route('/options', methods=['POST']) +def options_post() -> werkzeug.Response: + action = flask.request.form['action'] + + state = _app.get_haketilo_state() + + if action == 'use_enabled': + state.update_settings(mapping_use_mode=st.MappingUseMode.WHEN_ENABLED) + elif action == 'use_auto': + state.update_settings(mapping_use_mode=st.MappingUseMode.AUTO) + elif action == 'use_question': + state.update_settings(mapping_use_mode=st.MappingUseMode.QUESTION) + elif action == 'allow_scripts': + state.update_settings(default_allow_scripts=True) + elif action == 'block_scripts': + state.update_settings(default_allow_scripts=False) + else: + raise ValueError() + + return flask.redirect(flask.url_for('.options'), 303) diff --git a/src/hydrilla/proxy/web_ui/prompts.py b/src/hydrilla/proxy/web_ui/prompts.py index b546e47..58b7906 100644 --- a/src/hydrilla/proxy/web_ui/prompts.py +++ b/src/hydrilla/proxy/web_ui/prompts.py @@ -42,30 +42,37 @@ from . import _app bp = flask.Blueprint('prompts', __package__) -@bp.route('/auto_install_error', methods=['GET']) -def auto_install_error_prompt() -> werkzeug.Response: - state = _app.get_haketilo_state() +def deserialized_request_details(salt: str) -> t.Mapping[str, str]: serializer = URLSafeSerializer( - state.get_secret(), - salt = 'auto_install_error' + _app.get_haketilo_state().get_secret(), + salt = salt ) + return serializer.loads(flask.request.args['details']) + + +@bp.route('/auto_install_error', methods=['GET']) +def auto_install_error_prompt(errors: t.Mapping[str, bool] = {}) \ + -> werkzeug.Response: try: - details: t.Mapping[str, str] = \ - serializer.loads(flask.request.args['details']) + details = deserialized_request_details('auto_install_error') except: return flask.redirect(flask.url_for('home')) try: - payload_ref = state.payload_store().get(details['payload_id']) + payload_store = _app.get_haketilo_state().payload_store() + payload_ref = payload_store.get(details['payload_id']) display_info = payload_ref.get_display_info() + if not display_info.has_problems: + return flask.redirect(details['next_url']) + html = flask.render_template( 'prompts/auto_install_error.html.jinja', display_info = display_info, - next_url = details['next_url'] + **errors ) return flask.make_response(html, 200) except st.MissingItemError: @@ -73,12 +80,16 @@ def auto_install_error_prompt() -> werkzeug.Response: @bp.route('/auto_install_error', methods=['POST']) def auto_install_error_prompt_post() -> werkzeug.Response: + try: + details = deserialized_request_details('auto_install_error') + except: + return flask.redirect(flask.url_for('home'), code=303) + form_data = flask.request.form action = form_data['action'] mapping_ver_id = str(int(form_data['mapping_ver_id'])) - payload_id = str(int(form_data['payload_id'])) - next_url = form_data['next_url'] + payload_id = str(int(details['payload_id'])) state = _app.get_haketilo_state() @@ -92,24 +103,81 @@ def auto_install_error_prompt_post() -> werkzeug.Response: if action == 'disable_mapping': mapping_ver_ref.update_mapping_status(st.EnabledStatus.DISABLED) elif action == 'retry_install': - payload_ref.ensure_items_installed() + payload_ref.ensure_items_installed() else: raise ValueError() - except (st.RepoCommunicationError, st.FileInstallationError): - params = {'payload_id': payload_id, 'next_url': next_url} + except st.RepoCommunicationError: + assert action == 'retry_install' + return auto_install_error_prompt({'repo_communication_error': True}) + except st.FileInstallationError: + assert action == 'retry_install' + return auto_install_error_prompt({'file_installation_error': True}) + except st.MissingItemError: + flask.abort(404) - serializer = URLSafeSerializer( - state.get_secret(), - salt = 'auto_install_error' - ) - query = urlencode({'details': params}) - redirect_url = flask.url_for( - '.auto_install_error_prompt', - details = serializer.dumps(params) + return flask.redirect(details['next_url']) + + +@bp.route('/package_suggestion', methods=['GET']) +def package_suggestion_prompt(errors: t.Mapping[str, bool] = {}) \ + -> werkzeug.Response: + try: + details = deserialized_request_details('package_suggestion') + except: + return flask.redirect(flask.url_for('home')) + + try: + payload_store = _app.get_haketilo_state().payload_store() + payload_ref = payload_store.get(details['payload_id']) + + display_info = payload_ref.get_display_info() + + if display_info.mapping_info.active != st.ActiveStatus.AUTO: + return flask.redirect(details['next_url']) + + html = flask.render_template( + 'prompts/package_suggestion.html.jinja', + display_info = display_info, + **errors ) + return flask.make_response(html, 200) + except st.MissingItemError: + flask.abort(404) + +@bp.route('/package_suggestion', methods=['POST']) +def package_suggestion_prompt_post() -> werkzeug.Response: + try: + details = deserialized_request_details('package_suggestion') + except: + return flask.redirect(flask.url_for('home')) + + form_data = flask.request.form + action = form_data['action'] - return flask.redirect(redirect_url) + mapping_ver_id = str(int(form_data['mapping_ver_id'])) + + state = _app.get_haketilo_state() + + try: + mapping_ver_store = state.mapping_version_store() + mapping_ver_ref = mapping_ver_store.get(mapping_ver_id) + + if action == 'disable_mapping': + mapping_ver_ref.update_mapping_status(st.EnabledStatus.DISABLED) + elif action == 'enable_mapping': + mapping_ver_ref.update_mapping_status( + enabled = st.EnabledStatus.ENABLED, + frozen = st.FrozenStatus.EXACT_VERSION + ) + else: + raise ValueError() + except st.RepoCommunicationError: + assert action == 'enable_mapping' + return package_suggestion_prompt({'repo_communication_error': True}) + except st.FileInstallationError: + assert action == 'enable_mapping' + return package_suggestion_prompt({'file_installation_error': True}) except st.MissingItemError: flask.abort(404) - return flask.redirect(next_url) + return flask.redirect(details['next_url']) diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py index ff7c1f7..855345e 100644 --- a/src/hydrilla/proxy/web_ui/root.py +++ b/src/hydrilla/proxy/web_ui/root.py @@ -44,6 +44,7 @@ from ...translations import translation as make_translation from ... import item_infos from .. import state as st from .. import http_messages +from . import options from . import repos from . import items from . import prompts @@ -89,10 +90,11 @@ class WebUIAppImpl(_app.WebUIApp): self.jinja_env.globals['InstalledStatus'] = st.InstalledStatus self.jinja_env.globals['ActiveStatus'] = st.ActiveStatus self.jinja_env.globals['ItemType'] = item_infos.ItemType + self.jinja_env.globals['MappingUseMode'] = st.MappingUseMode self.before_request(authenticate_by_referrer) - for blueprint in [repos.bp, items.bp, prompts.bp]: + for blueprint in [repos.bp, items.bp, prompts.bp, options.bp]: self.register_blueprint(blueprint) # Flask app is not thread-safe and has to be accompanied by an ugly lock. This diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja index 918fb28..06af7d9 100644 --- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja @@ -20,9 +20,9 @@ in a proprietary work, I am not going to enforce this in court. -#} <!DOCTYPE html> -{% macro button_row(buttons_data) %} +{% macro button_row(buttons_data, common_fields={}) %} <div class="flex-row"> - {% for classes, text, action in buttons_data %} + {% for classes, text, extra_fields in buttons_data %} {% if not loop.first %} <div class="button-row-separator"></div> {% do classes.append('button-bordering-left') %} @@ -39,7 +39,13 @@ in a proprietary work, I am not going to enforce this in court. {% endif %} <form method="POST" class="flex-row"> - <input name="action" value="{{ action }}" type="hidden"> + {% for name, value in extra_fields.items() %} + <input name="{{ name }}" value="{{ value }}" type="hidden"> + {% endfor %} + {% for name, value in common_fields.items() %} + <input name="{{ name }}" value="{{ value }}" type="hidden"> + {% endfor %} + <button class="{{ classes|join(' ') }}"{{ disabled_attr }}> {{ text }} </button> @@ -92,6 +98,10 @@ in a proprietary work, I am not going to enforce this in court. background-color: #fcc; } + #main > .error-note:first-child { + margin-top: 10px; + } + .block-with-bottom-margin, .flex-row, aside, p { display: block; margin: 0 0 10px 0; @@ -226,6 +236,7 @@ in a proprietary work, I am not going to enforce this in court. {% set navigation_bar = [ ('home', _('web_ui.base.nav.home')), + ('options.options', _('web_ui.base.nav.options')), ('items.packages', _('web_ui.base.nav.packages')), ('items.libraries', _('web_ui.base.nav.libraries')), ('repos.repos', _('web_ui.base.nav.repos')), diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja index f1d34cc..edfb772 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja @@ -91,10 +91,14 @@ in a proprietary work, I am not going to enforce this in court. {% do uninstall_but_classes.append('disabled-button') %} {% endif %} {% endif %}{# else/ version_display_info.installed == InstalledStatus.FA... #} + + {% set uninstall_fields = {'action': 'uninstall_item_version'} %} + {% set install_fields = {'action': 'install_item_version'} %} + {{ button_row([ - (uninstall_but_classes, uninstall_text, 'uninstall_item_version'), - (install_but_classes, install_text, 'install_item_version') + (uninstall_but_classes, uninstall_text, uninstall_fields), + (install_but_classes, install_text, install_fields) ]) }} {% endblock main_info %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja index 1eb9878..c9448e7 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja @@ -89,9 +89,9 @@ in a proprietary work, I am not going to enforce this in court. {{ button_row([ - (disable_but_classes, disable_text, 'disable_item'), - (unenable_but_classes, unenable_text, 'unenable_item'), - (enable_but_classes, enable_text, 'enable_item_version') + (disable_but_classes, disable_text, {'action': 'disable_item'}), + (unenable_but_classes, unenable_text, {'action': 'unenable_item'}), + (enable_but_classes, enable_text, {'action': 'enable_item_version'}) ]) }} @@ -182,9 +182,9 @@ in a proprietary work, I am not going to enforce this in court. {{ button_row([ - (unpin_but_classes, unpin_text, 'unfreeze_item'), - (pin_repo_but_classes, pin_repo_text, 'freeze_to_repo'), - (pin_ver_but_classes, pin_ver_text, 'freeze_to_version') + (unpin_but_classes, unpin_text, {'action': 'unfreeze_item'}), + (pin_repo_but_classes, pin_repo_text, {'action': 'freeze_to_repo'}), + (pin_ver_but_classes, pin_ver_text, {'action': 'freeze_to_version'}) ]) }} diff --git a/src/hydrilla/proxy/web_ui/templates/options.html.jinja b/src/hydrilla/proxy/web_ui/templates/options.html.jinja new file mode 100644 index 0000000..69fc1b0 --- /dev/null +++ b/src/hydrilla/proxy/web_ui/templates/options.html.jinja @@ -0,0 +1,86 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy web UI settings page. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use this code +in a proprietary work, I am not going to enforce this in court. +#} +{% extends "base.html.jinja" %} + +{% block title %} {{ _('web_ui.options.title') }} {% endblock %} + +{% block main %} + <h3> + {{ _('web_ui.options.heading') }} + </h3> + + {% set use_enabled_but_classes = ['green-button'] %} + {% set use_auto_but_classes = ['green-button'] %} + {% set use_question_but_classes = ['green-button'] %} + + <p> + {% if settings.mapping_use_mode == MappingUseMode.WHEN_ENABLED %} + {% do use_enabled_but_classes.append('disabled-button') %} + {{ _('web_ui.options.packages_are_used_when_enabled') }} + {% elif settings.mapping_use_mode == MappingUseMode.QUESTION %} + {% do use_question_but_classes.append('disabled-button') %} + {{ _('web_ui.options.user_gets_asked_whether_to_enable_package') }} + {% else %} + {# settings.mapping_use_mode == MappingUseMode.AUTO #} + {% do use_auto_but_classes.append('disabled-button') %} + {{ _('web_ui.options.packages_are_used_automatically') }} + {% endif %} + </p> + + {{ + button_row([ + (use_enabled_but_classes, + _('web_ui.options.use_enabled_button'), + {'action': 'use_enabled'}), + (use_question_but_classes, + _('web_ui.options.use_question_button'), + {'action': 'use_question'}), + (use_auto_but_classes, + _('web_ui.options.use_auto_button'), + {'action': 'use_auto'}) + ]) + }} + + <div class="horizontal-separator"></div> + + {% set allow_but_classes = ['red-button'] %} + {% set block_but_classes = ['green-button'] %} + + <p> + {% if settings.default_allow_scripts %} + {% do allow_but_classes.append('disabled-button') %} + {{ _('web_ui.options.scripts_are_allowed_by_default') }} + {% else %} + {% do block_but_classes.append('disabled-button') %} + {{ _('web_ui.options.scripts_are_blocked_by_default') }} + {% endif %} + </p> + + {% set allow_but_text = _('web_ui.options.allow_scripts_button') %} + {% set block_but_text = _('web_ui.options.block_scripts_button') %} + + {{ + button_row([ + (allow_but_classes, allow_but_text, {'action': 'allow_scripts'}), + (block_but_classes, block_but_text, {'action': 'block_scripts'}) + ]) + }} +{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja index 01f5c19..82a12e5 100644 --- a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja @@ -24,19 +24,15 @@ in a proprietary work, I am not going to enforce this in court. {{ _('web_ui.prompts.auto_install_error.title') }} {% endblock %} -{% macro button_form(action, button_class, button_text) %} - <form class="flex-row" method="POST"> - <input name="next_url" value="{{ next_url }}" type="hidden"> - <input name="payload_id" value="{{ display_info.ref.id }}" type="hidden"> - {% set mapping_ver_id = display_info.mapping_info.ref.id %} - <input name="mapping_ver_id" value="{{ mapping_ver_id }}" type="hidden"> - - <input name="action" value="{{ action }}" type="hidden"> - <button class="{{ button_class }}">{{ button_text }}</button> - </form> -{% endmacro %} - {% block main %} + {% if file_installation_error is defined %} + {{ error_note(_('web_ui.err.retry_install.file_installation_error')) }} + {% endif %} + + {% if repo_communication_error is defined %} + {{ error_note(_('web_ui.err.retry_install.repo_communication_error')) }} + {% endif %} + <h3> {{ _('web_ui.prompts.auto_install_error.heading') }} </h3> @@ -48,13 +44,14 @@ in a proprietary work, I am not going to enforce this in court. }} </p> - <div class="flex-row"> - {% set but_text = _('web_ui.prompts.auto_install_error.disable_button') %} - {{ button_form('disable_mapping', 'red-button', but_text) }} - - <div class="button-row-separator"></div> + {% set disable_text = _('web_ui.prompts.auto_install_error.disable_button') %} + {% set retry_text = _('web_ui.prompts.auto_install_error.retry_button') %} - {% set but_text = _('web_ui.prompts.auto_install_error.retry_button') %} - {{ button_form('retry_install', 'green-button', but_text) }} - </div> + {{ + button_row([ + (['red-button'], disable_text, {'action': 'disable_mapping'}), + (['green-button'], retry_text, {'action': 'retry_install'}) + ], {'mapping_ver_id': display_info.mapping_info.ref.id} + ) + }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja new file mode 100644 index 0000000..ea906bb --- /dev/null +++ b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja @@ -0,0 +1,58 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy web UI page that asks whether to enable a package that can be used with +current site. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use this code +in a proprietary work, I am not going to enforce this in court. +#} +{% extends "base.html.jinja" %} + +{% block title %} + {{ _('web_ui.prompts.package_suggestion.title') }} +{% endblock %} + +{% block main %} + {% if file_installation_error is defined %} + {{ error_note(_('web_ui.err.file_installation_error')) }} + {% endif %} + + {% if repo_communication_error is defined %} + {{ error_note(_('web_ui.err.repo_communication_error')) }} + {% endif %} + + <h3> + {{ _('web_ui.prompts.package_suggestion.heading') }} + </h3> + + <p> + {{ + _('web_ui.prompts.package_suggestion.do_you_want_to_enable_package_{}') + .format(display_info.mapping_info.info.long_name) + }} + </p> + + {% set disable_text = _('web_ui.prompts.package_suggestion.disable_button') %} + {% set enable_text = _('web_ui.prompts.package_suggestion.enable_button') %} + + {{ + button_row([ + (['red-button'], disable_text, {'action': 'disable_mapping'}), + (['blue-button'], enable_text, {'action': 'enable_mapping'}) + ], {'mapping_ver_id': display_info.mapping_info.ref.id} + ) + }} +{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja index 448c451..2c695d0 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja @@ -152,7 +152,8 @@ in a proprietary work, I am not going to enforce this in court. </p> {% set button_text = _('web_ui.repos.single.refresh_now_button') %} - {{ button_row([[['green-button'], button_text, 'refresh_repo']]) }} + {% set extra_fields = {'action': 'refresh_repo'} %} + {{ button_row([(['green-button'], button_text, extra_fields)]) }} </div> <div class="horizontal-separator"></div> @@ -171,6 +172,7 @@ in a proprietary work, I am not going to enforce this in court. <div class="horizontal-separator"></div> {% set button_text = _('web_ui.repos.single.remove_button') %} - {{ button_row([[['green-button'], button_text, 'remove_repo']]) }} + {% set extra_fields = {'action': 'remove_repo'} %} + {{ button_row([(['green-button'], button_text, extra_fields)]) }} {% endif %} {% endblock %} |