From 1fc2dbe5a3e867eb5e6c2f759409f1c94146a794 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Sat, 22 Oct 2022 11:19:14 +0200 Subject: [proxy] make popup script injection configurable throught the web UI --- src/hydrilla/proxy/state.py | 10 +- src/hydrilla/proxy/state_impl/concrete_state.py | 114 +++++++++++++++----- src/hydrilla/proxy/web_ui/root.py | 26 ++++- .../templates/import/checkbox_tricks.html.jinja | 9 +- .../include/checkbox_tricks_style.css.jinja | 10 ++ .../proxy/web_ui/templates/index.html.jinja | 119 ++++++++++++++++++++- .../web_ui/templates/repos/show_single.html.jinja | 4 +- .../web_ui/templates/rules/show_single.html.jinja | 2 +- 8 files changed, 258 insertions(+), 36 deletions(-) (limited to 'src/hydrilla/proxy') diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py index f047a68..d21a392 100644 --- a/src/hydrilla/proxy/state.py +++ b/src/hydrilla/proxy/state.py @@ -617,12 +617,12 @@ class HaketiloState(ABC): def update_settings( self, *, - mapping_use_mode: t.Optional[MappingUseMode] = None, - default_allow_scripts: t.Optional[bool] = None, - advanced_user: t.Optional[bool] = None, - repo_refresh_seconds: t.Optional[int] = None + mapping_use_mode: t.Optional[MappingUseMode] = None, + default_allow_scripts: t.Optional[bool] = None, + advanced_user: t.Optional[bool] = None, + repo_refresh_seconds: t.Optional[int] = None, + default_popup_settings: t.Mapping[str, PopupSettings] = {} ) -> None: - """....""" ... @property diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py index 2dd8810..5df7c34 100644 --- a/src/hydrilla/proxy/state_impl/concrete_state.py +++ b/src/hydrilla/proxy/state_impl/concrete_state.py @@ -55,6 +55,22 @@ from . import _operations here = Path(__file__).resolve().parent +def _add_popup_settings_columns(cursor: sqlite3.Cursor) -> None: + for page_type in ('jsallowed', 'jsblocked', 'payloadon'): + cursor.execute( + f''' + ALTER TABLE general ADD COLUMN + default_popup_{page_type}_onkeyboard BOOLEAN NOT NULL DEFAULT TRUE; + ''' + ) + cursor.execute( + f''' + ALTER TABLE general ADD COLUMN + default_popup_{page_type}_style CHAR(1) NOT NULL DEFAULT 'T' + CHECK (default_popup_{page_type}_style IN ('D', 'T')); + ''' + ) + def _prepare_database(connection: sqlite3.Connection) -> None: cursor = connection.cursor() @@ -74,19 +90,43 @@ def _prepare_database(connection: sqlite3.Connection) -> None: if not db_initialized: cursor.executescript((here / 'tables.sql').read_text()) - else: - cursor.execute( - ''' - SELECT - haketilo_version - FROM - general; - ''' - ) - (db_haketilo_version,) = cursor.fetchone() - if db_haketilo_version != '3.0b1': - raise HaketiloException(_('err.proxy.unknown_db_schema')) + cursor.execute('BEGIN TRANSACTION;') + + try: + if db_initialized: + # If db was initialized before we connected to it, we must check + # what its schema version is. + cursor.execute( + ''' + SELECT + haketilo_version + FROM + general; + ''' + ) + + (db_haketilo_version,) = cursor.fetchone() + if db_haketilo_version != '3.0b1': + raise HaketiloException(_('err.proxy.unknown_db_schema')) + + popup_settings_columns_present = False + + cursor.execute("PRAGMA TABLE_INFO('general')") + for __cid, name, __type, __notnull, __dflt_value, __pk \ + in cursor.fetchall(): + if name == 'default_popup_jsallowed_onkeyboard': + popup_settings_columns_present = True + + if not popup_settings_columns_present: + _add_popup_settings_columns(cursor) + + cursor.execute('COMMIT TRANSACTION;') + except: + cursor.execute('ROLLBACK TRANSACTION;') + raise + + cursor.execute('PRAGMA FOREIGN_KEYS;') if cursor.fetchall() == []: @@ -106,17 +146,35 @@ def load_settings(cursor: sqlite3.Cursor) -> st.HaketiloGlobalSettings: repo_refresh_seconds, mapping_use_mode FROM - general + general; ''' ) (default_allow_scripts, advanced_user, repo_refresh_seconds, mapping_use_mode), = cursor.fetchall() - default_popup_settings = st.PopupSettings( - keyboard_trigger = True, - style = st.PopupStyle.TAB - ) + popup_settings_dict = {} + + for page_type in ('jsallowed', 'jsblocked', 'payloadon'): + try: + cursor.execute( + f''' + SELECT + default_popup_{page_type}_onkeyboard, + default_popup_{page_type}_style + FROM + general; + ''' + ) + + (onkeyboard, style), = cursor.fetchall() + except: + onkeyboard, style = True, 'T' + + popup_settings_dict[f'default_popup_{page_type}'] = st.PopupSettings( + keyboard_trigger = onkeyboard, + style = st.PopupStyle(style) + ) return st.HaketiloGlobalSettings( default_allow_scripts = default_allow_scripts, @@ -124,9 +182,7 @@ def load_settings(cursor: sqlite3.Cursor) -> st.HaketiloGlobalSettings: repo_refresh_seconds = repo_refresh_seconds, mapping_use_mode = st.MappingUseMode(mapping_use_mode), - default_popup_jsallowed = default_popup_settings, - default_popup_jsblocked = default_popup_settings, - default_popup_payloadon = default_popup_settings + **popup_settings_dict ) @dc.dataclass @@ -360,10 +416,11 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): def update_settings( self, *, - mapping_use_mode: t.Optional[st.MappingUseMode] = None, - default_allow_scripts: t.Optional[bool] = None, - advanced_user: t.Optional[bool] = None, - repo_refresh_seconds: t.Optional[int] = None + mapping_use_mode: t.Optional[st.MappingUseMode] = None, + default_allow_scripts: t.Optional[bool] = None, + advanced_user: t.Optional[bool] = None, + repo_refresh_seconds: t.Optional[int] = None, + default_popup_settings: t.Mapping[str, st.PopupSettings] = {} ) -> None: with self.cursor(transaction=True) as cursor: def set_opt(col_name: str, val: t.Union[bool, int, str]) -> None: @@ -378,6 +435,15 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields): if repo_refresh_seconds is not None: set_opt('repo_refresh_seconds', repo_refresh_seconds) + for page_type in ('jsallowed', 'jsblocked', 'payloadon'): + popup_settings = default_popup_settings.get(page_type) + if popup_settings is not None: + trigger_col_name = f'default_popup_{page_type}_onkeyboard' + set_opt(trigger_col_name, popup_settings.keyboard_trigger) + + style_col_name = f'default_popup_{page_type}_style' + set_opt(style_col_name, popup_settings.style.value) + self.settings = load_settings(cursor) @staticmethod diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py index 3120d0e..d775730 100644 --- a/src/hydrilla/proxy/web_ui/root.py +++ b/src/hydrilla/proxy/web_ui/root.py @@ -26,9 +26,15 @@ # court. """ -..... +This module instantiated Flask apps responsible for the web UI and facilitates +conversion of Flask response objects to the ResponseInfo type used by other +Haketilo code. + +In addition, the Haketilo root/settings page and landing page also have their +handlers defined here. """ +import re import dataclasses as dc import typing as t @@ -128,6 +134,10 @@ def home(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: ) return flask.make_response(html, 200) +popup_toggle_action_re = re.compile( + r'^popup_(yes|no)_when_(jsallowed|jsblocked|payloadon)$' +) + @home_bp.route('/', methods=['POST']) def home_post() -> werkzeug.Response: action = flask.request.form['action'] @@ -151,7 +161,19 @@ def home_post() -> werkzeug.Response: elif action == 'prune_orphans': state.prune_orphan_items() else: - raise ValueError() + match = popup_toggle_action_re.match(action) + if match is None: + raise ValueError() + + popup_enable = match.group(1) == 'yes' + page_type = match.group(2) + + settings_prop = f'default_popup_{page_type}' + old_settings = getattr(state.get_settings(), settings_prop) + + new_settings = dc.replace(old_settings, keyboard_trigger=popup_enable) + + state.update_settings(default_popup_settings={page_type: new_settings}) return flask.redirect(flask.url_for('.home'), 303) diff --git a/src/hydrilla/proxy/web_ui/templates/import/checkbox_tricks.html.jinja b/src/hydrilla/proxy/web_ui/templates/import/checkbox_tricks.html.jinja index 999208b..4ad9ca1 100644 --- a/src/hydrilla/proxy/web_ui/templates/import/checkbox_tricks.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/import/checkbox_tricks.html.jinja @@ -23,7 +23,7 @@ code in a proprietary work, I am not going to enforce this in court. {{ name }}_chbx {%- endmacro %} -{% macro sibling_hider(button_text, name, initial_show=false) %} +{% macro sibling_hider_but(button_text, name, initial_show=false) %} {% set attrs = {'type': 'checkbox', 'class': 'chbx-tricks-show-hide'} %} {% do attrs.update({'id': hider_id(name)}) %} {% do attrs.update({'checked': none if initial_show else ''}) %} @@ -33,3 +33,10 @@ code in a proprietary work, I am not going to enforce this in court. {{ button_text }} {% endmacro %} + +{% macro sibling_hider_radio(name, radio_id, initial_show=false) %} + {% set attrs = {'type': 'radio', 'class': 'chbx-tricks-show'} %} + {% do attrs.update({'name': name, 'id': radio_id}) %} + {% do attrs.update({'checked': '' if initial_show else none}) %} + +{% endmacro %} diff --git a/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja b/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja index 6932228..a47a438 100644 --- a/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja +++ b/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja @@ -33,3 +33,13 @@ input.chbx-tricks-hide-show:checked+*, input.chbx-tricks-show-hide:not(:checked)+* { display: none !important; } + + +input.chbx-tricks-show, input.chbx-tricks-hide { + display: none !important; +} + +input.chbx-tricks-hide:checked+*, +input.chbx-tricks-show:not(:checked)+* { + display: none !important; +} diff --git a/src/hydrilla/proxy/web_ui/templates/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/index.html.jinja index 010c2ed..2b49361 100644 --- a/src/hydrilla/proxy/web_ui/templates/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/index.html.jinja @@ -22,6 +22,14 @@ code in a proprietary work, I am not going to enforce this in court. {% block title %} {{ _('web_ui.home.title') }} {% endblock %} +{% block style %} + {{ super() }} + + {% include 'include/checkbox_tricks_style.css.jinja' %} +{% endblock %} + +{% import 'import/checkbox_tricks.html.jinja' as tricks %} + {% block main %}

{{ _('web_ui.home.heading.welcome_to_haketilo') }} @@ -182,4 +190,113 @@ code in a proprietary work, I am not going to enforce this in court. ]) }} {% endif %} -{% endblock %} + +
+ + {{ label(_('web_ui.home.popup_settings_label')) }} + + {% + macro render_popup_settings( + page_type, + initial_show = false, + popup_change_but_base_classes = ['red-button', 'blue-button'] + ) + %} + {% set radio_id = 'popup_settings_radio_' ~ page_type %} + {{ tricks.sibling_hider_radio('popup_settings', radio_id, initial_show) }} + +
+

+ {{ _('web_ui.home.configure_popup_settings_on_pages_with') }} +

+ +
+ {% + for but_page_type, but_text in [ + ('jsallowed', _('web_ui.home.popup_settings_jsallowed_button')), + ('jsblocked', _('web_ui.home.popup_settings_jsblocked_button')), + ('payloadon', _('web_ui.home.popup_settings_payloadon_button')) + ] + %} + {% set attrs, classes = {}, ['green-button'] %} + + {% if but_page_type == page_type %} + {% do classes.append('disabled-button') %} + {% else %} + {% set but_radio_id = 'popup_settings_radio_' ~ but_page_type %} + {% do attrs.update({'for': but_radio_id}) %} + {% endif %} + + {% if not loop.first %} + {% do classes.append('button-bordering-left') %} + {% endif %} + {% if not loop.last %} + {% do classes.append('button-bordering-right') %} + {% endif %} + + {% do attrs.update({'class': classes|join(' ')}) %} + + + + {% if not loop.last %} +
+ {% endif %} + {% endfor %} +
+ + {% set popup_no_but_classes = [popup_change_but_base_classes[0]] %} + {% set popup_yes_but_classes = [popup_change_but_base_classes[1]] %} + + {% set settings_prop = 'default_popup_' ~ page_type %} + {% set is_on = (settings|attr(settings_prop)).keyboard_trigger %} + + {% if is_on %} + {% do popup_yes_but_classes.append('disabled-button') %} + {% else %} + {% do popup_no_but_classes.append('disabled-button') %} + {% endif %} + +

+ {{ caller(is_on) }} +

+ + {{ + button_row([ + (popup_no_but_classes, + _('web_ui.home.popup_no_button'), + {'action': 'popup_no_when_' ~ page_type}), + (popup_yes_but_classes, + _('web_ui.home.popup_yes_button'), + {'action': 'popup_yes_when_' ~ page_type}) + ]) + }} +
+ {% endmacro %} + + {% set but_classes = ['green-button', 'green-button'] %} + {% call(popup_is_on) render_popup_settings('jsallowed', true, but_classes) %} + {% if popup_is_on %} + {{ _('web_ui.home.jsallowed_popup_yes') }} + {% else %} + {{ _('web_ui.home.jsallowed_popup_no') }} + {% endif %} + {% endcall %} + + {% call(popup_is_on) render_popup_settings('jsblocked') %} + {% if popup_is_on %} + {{ _('web_ui.home.jsblocked_popup_yes') }} + {% else %} + {{ _('web_ui.home.jsblocked_popup_no') }} + {% endif %} + {% endcall %} + + {% call(popup_is_on) render_popup_settings('payloadon') %} + {% if popup_is_on %} + {{ _('web_ui.home.payloadon_popup_yes') }} + {% else %} + {{ _('web_ui.home.payloadon_popup_no') }} + {% endif %} + {% endcall %} +{% endblock main %} 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 c4b7a9a..8b070d9 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 @@ -59,7 +59,7 @@ code in a proprietary work, I am not going to enforce this in court. {% set button_text = _('web_ui.repos.single.update_name_button') %} {% set initial_show = repo_name_invalid is defined %} {% set initial_show = initial_show or repo_name_taken is defined %} - {{ tricks.sibling_hider(button_text, 'edit_name', initial_show) }} + {{ tricks.sibling_hider_but(button_text, 'edit_name', initial_show) }}
@@ -107,7 +107,7 @@ code in a proprietary work, I am not going to enforce this in court. {% set button_text = _('web_ui.repos.single.update_url_button') %} {% set initial_show = repo_url_invalid is defined %} - {{ tricks.sibling_hider(button_text, 'edit_url', initial_show) }} + {{ tricks.sibling_hider_but(button_text, 'edit_url', initial_show) }} diff --git a/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja index 95e8009..7d29a0d 100644 --- a/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja @@ -47,7 +47,7 @@ code in a proprietary work, I am not going to enforce this in court. {% set button_text = _('web_ui.rules.single.update_pattern_button') %} {% set initial_show = rule_pattern_invalid is defined %} - {{ tricks.sibling_hider(button_text, 'edit_pattern', initial_show) }} + {{ tricks.sibling_hider_but(button_text, 'edit_pattern', initial_show) }} -- cgit v1.2.3