aboutsummaryrefslogtreecommitdiff
path: root/src/hydrilla/proxy
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-10-22 11:19:14 +0200
committerWojtek Kosior <koszko@koszko.org>2022-10-22 13:22:40 +0200
commit1fc2dbe5a3e867eb5e6c2f759409f1c94146a794 (patch)
tree73e9b90b26fb30e115ae111bb7592fdb92615cd3 /src/hydrilla/proxy
parentfe43bd552aaacd649b0e00afada01d07ad8dae9a (diff)
downloadhaketilo-hydrilla-1fc2dbe5a3e867eb5e6c2f759409f1c94146a794.tar.gz
haketilo-hydrilla-1fc2dbe5a3e867eb5e6c2f759409f1c94146a794.zip
[proxy] make popup script injection configurable throught the web UI
Diffstat (limited to 'src/hydrilla/proxy')
-rw-r--r--src/hydrilla/proxy/state.py10
-rw-r--r--src/hydrilla/proxy/state_impl/concrete_state.py114
-rw-r--r--src/hydrilla/proxy/web_ui/root.py26
-rw-r--r--src/hydrilla/proxy/web_ui/templates/import/checkbox_tricks.html.jinja9
-rw-r--r--src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja10
-rw-r--r--src/hydrilla/proxy/web_ui/templates/index.html.jinja119
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja4
-rw-r--r--src/hydrilla/proxy/web_ui/templates/rules/show_single.html.jinja2
8 files changed, 258 insertions, 36 deletions
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 }}
</label>
{% 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}) %}
+ <input {{ attrs|xmlattr }}>
+{% 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 %}
<h3>
{{ _('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 %}
+
+ <div class="horizontal-separator"></div>
+
+ {{ 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) }}
+
+ <div>
+ <p>
+ {{ _('web_ui.home.configure_popup_settings_on_pages_with') }}
+ </p>
+
+ <div class="flex-row">
+ {%
+ 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(' ')}) %}
+
+ <label {{ attrs|xmlattr }}>
+ {{ but_text }}
+ </label>
+
+ {% if not loop.last %}
+ <div class="button-row-separator"></div>
+ {% endif %}
+ {% endfor %}
+ </div>
+
+ {% 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 %}
+
+ <p>
+ {{ caller(is_on) }}
+ </p>
+
+ {{
+ 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})
+ ])
+ }}
+ </div>
+ {% 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) }}
<form method="POST">
<input type="hidden" name="action" value="update_repo_data">
@@ -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) }}
<form method="POST">
<input type="hidden" name="action" value="update_repo_data">
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) }}
<form method="POST">
<input type="hidden" name="action" value="update_rule_data">