From b6a085b5385b52ff742d2f5c5220bda2359145c4 Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Sun, 3 Jul 2022 19:13:58 -0400 Subject: [PATCH] store passwords in database --- barkshark_web/component/status_bar.py | 6 +- barkshark_web/component/window.py | 2 - barkshark_web/database/base.py | 65 +++- barkshark_web/database/migrate.py | 7 +- barkshark_web/database/rows.py | 25 +- barkshark_web/extension_api.py | 21 +- barkshark_web/extensions.py | 42 ++- barkshark_web/localweb/js/functions.js | 11 + .../localweb/page/password-edit.haml | 13 +- barkshark_web/localweb/page/passwords.haml | 2 +- barkshark_web/objects/login_rows.py | 17 +- barkshark_web/passwords.py | 335 ------------------ barkshark_web/protocol/local.py | 78 ++-- webextension | 1 - webextension/meson.build | 10 + webextension/webextension.vala | 121 +++++++ 16 files changed, 314 insertions(+), 442 deletions(-) delete mode 100644 barkshark_web/passwords.py delete mode 160000 webextension create mode 100644 webextension/meson.build create mode 100644 webextension/webextension.vala diff --git a/barkshark_web/component/status_bar.py b/barkshark_web/component/status_bar.py index d1684d8..354292b 100644 --- a/barkshark_web/component/status_bar.py +++ b/barkshark_web/component/status_bar.py @@ -3,7 +3,6 @@ from ..database import default_permissions from ..exceptions import AccountNotFoundError, NoAccountsError from ..functions import SignalBlock, connect, get_buffer_text from ..objects.login_rows import SavedLoginRow -from ..passwords import passdb class StatusBar: @@ -392,8 +391,9 @@ class StatusBar: for child in login_list.get_children(): child.destroy() - for row in passdb.fetch(domain=self.window.active_tab.url.hostname()): - login_list.add(SavedLoginRow(row, self.window.active_tab.url)['container']) + with self.db.session as s: + for row in s.fetch('passwords', domain=self.window.active_tab.url.hostname()): + login_list.add(SavedLoginRow(row, self.window.active_tab.url)['container']) def handle_toot_key_press(self, textview, event, *args): diff --git a/barkshark_web/component/window.py b/barkshark_web/component/window.py index 4eb9399..04c7dfd 100644 --- a/barkshark_web/component/window.py +++ b/barkshark_web/component/window.py @@ -7,7 +7,6 @@ from .web_view import Webview from .. import var, __software__ from ..functions import Thread, run_in_gui_thread, connect, get_app, icon_set -from ..passwords import passdb from ..themes import Themes from ..widgets import FileChooser, Menu, MenuButtonRefresh @@ -548,7 +547,6 @@ class Window(Gtk.ApplicationWindow): def handle_window_close(self, *args): logging.verbose('Saving data') - passdb.disconnect() self.save_tabs() with self.app.db.session as s: diff --git a/barkshark_web/database/base.py b/barkshark_web/database/base.py index 1c4d012..accd192 100644 --- a/barkshark_web/database/base.py +++ b/barkshark_web/database/base.py @@ -78,6 +78,17 @@ default_searches = { } +password_fields = [ + 'username', + 'domain', + 'label', + 'password', + 'label', + 'url', + 'note' +] + + tables = { 'config': [ Column('id'), @@ -160,23 +171,16 @@ tables = { Column('count', 'integer', nullable=False, default=0), Column('last_access', 'datetime') ], - 'passfields': [ - Column('id'), - Column('domain', 'text', nullable=False), - Column('url', 'text', nullable=False), - Column('userfield', 'text', nullable=False), - Column('passfield', 'text', nullable=False) - ], 'passwords': [ Column('id'), Column('username', 'text', nullable=False), Column('password', 'text', nullable=False), - Column('name', 'text', nullable=False), - Column('domain', 'text'), + Column('label', 'text', nullable=False), + Column('domain', 'text', nullable=False), Column('url', 'text'), - Column('note', 'text', nullable=False), + Column('note', 'text'), Column('created', 'datetime', nullable=False), - Column('modified', 'datetime', nullable=False) + Column('modified', 'datetime') ] } @@ -290,8 +294,8 @@ class CustomSession(Session): return self.get_cached('history', url, url=url) - def get_passfield(self, url): - return self.get_cached('passfields', url.without_query, url=url.without_query) + def get_password(self, username, host): + return self.fetch('passwords', username=username, domain=host).one() def get_permission(self, domain, cache=True): @@ -453,6 +457,41 @@ class CustomSession(Session): return self.put_history(tab.url, tab.title, (True if tab.fedi_post else False)) + def put_password(self, username, domain, password, url=None, label=None, note=None): + data = DotDict( + username = username, + domain = domain, + password = password, + created = datetime.now() + ) + + for key, value in (('url', url), ('label', label), ('note', note)): + if value: + data[key] = value + + if (row := self.get_password(username, domain)): + return self.put_password_row(row, **data) + + if not data.get('label'): + data['label'] = f'{username} @ {domain}' + + self.insert('passwords', **data) + + return self.get_password(username, domain) + + + def put_password_row(self, row, **kwargs): + for key in kwargs.keys(): + if key not in password_fields: + raise KeyError(f'Invalid password field: {key}') + + kwargs.update({'modified': datetime.now()}) + self.update_row(row, **kwargs) + row.update(**kwargs) + + return row + + def put_passfield(self, url, userfield, passfield): if None in [userfield, passfield]: logging.verbose('Not inserting empty login fields') diff --git a/barkshark_web/database/migrate.py b/barkshark_web/database/migrate.py index ec7f34f..6c90840 100644 --- a/barkshark_web/database/migrate.py +++ b/barkshark_web/database/migrate.py @@ -4,7 +4,6 @@ from .base import tables from .. import var from ..functions import get_app -from ..passwords import passdb def now(db, version): @@ -33,11 +32,9 @@ def now(db, version): for keyword, data in s.default_searches.items(): s.put_search(data[0], keyword, data[1]) - if version <= 20210905: + if version < 20210905: s.create_tables({'passwords': tables['passwords']}) - - else: - raise KeyError('heck') + s.drop_table('passfields') s.put_config('version', var.dbversion, 'int') diff --git a/barkshark_web/database/rows.py b/barkshark_web/database/rows.py index cc49cc2..f50276a 100644 --- a/barkshark_web/database/rows.py +++ b/barkshark_web/database/rows.py @@ -7,7 +7,7 @@ from mastodon import Mastodon from PIL import Image from urllib.parse import quote_plus -from ..functions import get_app +from ..functions import TimeoutCallback, get_app, run_in_gui_thread row_classes = {} @@ -22,8 +22,14 @@ def register_class(table): return wrapper +class RowBase(Row): + @property + def app(self): + return get_app() + + @register_class('accounts') -class Account(Row): +class Account(RowBase): _api = None _emojis = None @@ -178,8 +184,21 @@ class Account(Row): s.put_config('active_acct', self.id) +@register_class('passwords') +class Password(RowBase): + def copy_password(self, timeout=60): + self.app.set_clipboard_text(self.password) + + timer = TimeoutCallback(timeout, run_in_gui_thread, self.app.handle_clipboard_clear_password, self.password) + timer.start() + + + def copy_username(self): + self.app.set_clipboard_text(self.username) + + @register_class('search') -class Search(Row): +class Search(RowBase): def compile(self, text): if text.startswith(f'{self.keyword} '): text = text[len(self.keyword)+1:] diff --git a/barkshark_web/extension_api.py b/barkshark_web/extension_api.py index a9e8021..ee024ca 100644 --- a/barkshark_web/extension_api.py +++ b/barkshark_web/extension_api.py @@ -37,6 +37,15 @@ class BarksharkWebExtension: self.enabled = True + def __getattr__(self, key): + try: + if object.__getattribute__(self, 'manifest'): + return self.manifest[key] + + finally: + return object.__getattribute(self, key) + + def __getitem__(self, key): return self.config[key] @@ -91,23 +100,23 @@ class BarksharkWebExtension: self.logging.setConfig({'level': level}) - def handle_initialize(self, client, tab): + def handle_initialize(self): pass - def handle_page_created(self, client, tab): + def handle_page_created(self, pageid: int): pass - def handle_document_loaded(self, client, tab, page_url: str): + def handle_document_loaded(self, page_url: Url): pass - def handle_send_request(self, client, tab, page_url: str, request: dict, redirect: dict): + def handle_send_request(self, page_url: Url, redirect: Url): pass - def handle_console_message(self, client, tab, page_url: str, text: str, level: str, line: int, source: str, source_id: int): + def handle_console_message_sent(self, text: str, level: str, line: int): pass @@ -115,5 +124,5 @@ class BarksharkWebExtension: pass - def handle_form_sent(self, data, stage): + def handle_form_sent(self, stage: str, fields: DotDict): pass diff --git a/barkshark_web/extensions.py b/barkshark_web/extensions.py index 50df848..de55585 100644 --- a/barkshark_web/extensions.py +++ b/barkshark_web/extensions.py @@ -86,40 +86,50 @@ class WebExtensions(ObjectBase): assert message.get_name() not in ['command'] try: - func = getattr(self, f'handle_{cmd_name}') + func = getattr(self, f'parse_{cmd_name}') + except AttributeError: return logging.error(f'Invalid command: {cmd_name}') - func(*message.get_parameters()) + try: + args, kwargs = func(*message.get_parameters()) - - def handle_page_created(self, page_id): - return - print(f'page-created: {page_id}') - - - def handle_form_sent(self, stage, raw_data): - raw_data = DotDict(raw_data) + except: + traceback.print_exc() + logging.error('Error parsing data for signal:', cmd_name) + return for ext in (*self.system.values(), *self.user.values()): try: - ext.handle_form_sent( - DotDict(dict(zip(raw_data['keys'], raw_data['values']))), - stage.replace('WEBKIT_FORM_SUBMISSION_WILL_', '') - ) + getattr(ext, f'handle_{cmd_name}')(*args, **kwargs) except: traceback.print_exc() logging.error('Error handling handle_form_sent for extension:', ext.name) - def handle_console_message_sent(self, text, level, line): + def parse_page_created(self, page_id): + return (page_id, ), {} + + + def parse_form_sent(self, stage, raw_data): + raw_data = DotDict(raw_data) + + args = [ + DotDict(dict(zip(raw_data['keys'], raw_data['values']))), + stage.replace('WEBKIT_FORM_SUBMISSION_WILL_', '') + ] + + return args, {} + + + def parse_console_message_sent(self, text, level, line): log_level = level.replace('WEBKIT_CONSOLE_MESSAGE_LEVEL_', '').upper() if log_level == 'LOG': log_level = 'INFO' - #logging.log(LogLevel[log_level], text.strip()) + return (text, level, line), {} class ExtPasswordManager(BarksharkWebExtension): diff --git a/barkshark_web/localweb/js/functions.js b/barkshark_web/localweb/js/functions.js index bab116c..97bc8d9 100644 --- a/barkshark_web/localweb/js/functions.js +++ b/barkshark_web/localweb/js/functions.js @@ -175,3 +175,14 @@ function copy_password(id) { } }); } + + +function setup_password_handlers() { + connect_event("password", "focus", (event) => { + event.currentTarget.type = "text"; + }); + + connect_event("password", "blur", (event) => { + event.currentTarget.type = "password"; + }); +} diff --git a/barkshark_web/localweb/page/password-edit.haml b/barkshark_web/localweb/page/password-edit.haml index 417c3a9..ba8b6b1 100644 --- a/barkshark_web/localweb/page/password-edit.haml +++ b/barkshark_web/localweb/page/password-edit.haml @@ -1,6 +1,6 @@ -if password -set page = 'Edit Password: ' + password.label - -set action = var.local + '/passwords/update/' + password.id + -set action = var.local + '/passwords/update/' + str(password.id) -else -set page = 'New Password' -set action = var.local + '/passwords/update' @@ -15,18 +15,18 @@ %input type='hidden' name='redir' value='/passwords' .grid-container + %label.grid-item for='label' << Label + %input.grid-item id='label' class='full-width' type='text' name='label' placeholder='label' value='{{password.label or ""}}' + %label.grid-item for='username' << Username %input.grid-item id='username' class='full-width' type='text' name='username' placeholder='username' value='{{password.username}}' %label.grid-item for='password' << Password - %input.grid-item id='password' class='full-width' type='password' name='password' placeholder='password' value='{{password.password}}' + %input#password.grid-item id='password' class='full-width' type='password' name='password' placeholder='password' value='{{password.password}}' %label.grid-item for='domain' << Domain %input.grid-item id='domain' class='full-width' type='text' name='domain' placeholder='domain' value='{{password.domain}}' - %label.grid-item for='label' << Label - %input.grid-item id='label' class='full-width' type='text' name='label' placeholder='label' value='{{password.label}}' - %label.grid-item for='url' << Url %input.grid-item id='url' class='full-width' type='text' name='url' placeholder='url' value='{{password.url or ""}}' @@ -40,3 +40,6 @@ %a.button onclick='wine.location="{{var.local}}/passwords/delete/{{password.id}}?redir=/passwords"' << Delete %a.button onclick='window.location.href="{{var.local}}/passwords"' << Cancel + + %script type='application/javascript' + setup_password_handlers() diff --git a/barkshark_web/localweb/page/passwords.haml b/barkshark_web/localweb/page/passwords.haml index 98c40f4..da1ef42 100644 --- a/barkshark_web/localweb/page/passwords.haml +++ b/barkshark_web/localweb/page/passwords.haml @@ -16,7 +16,7 @@ -if items -for row in items - %details.item id='{{row.id}}' open + %details.item id='{{row.id}}' %summary << {{row.label}} .details-indent diff --git a/barkshark_web/objects/login_rows.py b/barkshark_web/objects/login_rows.py index 4cf7d78..3ce61ce 100644 --- a/barkshark_web/objects/login_rows.py +++ b/barkshark_web/objects/login_rows.py @@ -1,5 +1,4 @@ from ..functions import TimeoutCallback, connect, get_app, run_in_gui_thread -from ..passwords import passdb class LoginRowBase: @@ -14,7 +13,7 @@ class LoginRowBase: @property def db(self): - return self.ap.db + return self.app.db @property @@ -35,14 +34,8 @@ class SavedLoginRow(LoginRowBase): self.ui = Gtk.Builder.new_from_file(self.app.path.resources.join('password_saved.ui')) self.row = row - with self.db.session as s: - self.dbrow = s.get_passfield(page_url) - self['username'].set_text(self.row['username']) - #if self.row.url != page_url or not self.dbrow: - #self['fill'].set_sensitive(False) - self.connect('fill', 'clicked', self.handle_fill_password) self.connect('copy-password', 'clicked', self.handle_copy_password) self.connect('copy-username', 'clicked', self.row.copy_username) @@ -75,9 +68,6 @@ class UnsavedLoginRow(LoginRowBase): self.ext = ext self.form = form - with self.db.session as s: - self.row = s.get_passfield(form.form_url) - self.set_data(form.username[1], form.password[1]) self.connect('save', 'clicked', self.handle_save) @@ -123,6 +113,7 @@ class UnsavedLoginRow(LoginRowBase): def handle_save(self): data = self.get_data() - passdb.insert(data.username, data.url.domain, data.password, data.url, data.note) - self.window.passwords.refresh() + with get_app().db.session as s: + s.put_password(data.username, data.password, data.url, name=None, note=data.note) + self.handle_cancel() diff --git a/barkshark_web/passwords.py b/barkshark_web/passwords.py deleted file mode 100644 index d313b84..0000000 --- a/barkshark_web/passwords.py +++ /dev/null @@ -1,335 +0,0 @@ -import json, secretstorage - -from datetime import datetime -from secretstorage.collection import Collection, get_collection_by_alias, create_collection -from secretstorage.exceptions import ItemNotFoundException, LockedException - -from .functions import TimeoutCallback, get_app, run_in_gui_thread - - -pass_store_keys = ['username', 'domain', 'url', 'note'] -pass_keys = [*pass_store_keys, 'created', 'modified', 'label', 'password'] - - -def parse_data(username=None, domain=None, password=None, url=None, note=None, label=None, id=None, **kwargs): - new_data = DotDict( - id = id, - username = username, - domain = domain, - url = url, - note = note, - password = password, - label = label - ) - - for key, value in tuple(new_data.items()): - if not value: - del new_data[key] - - return new_data - - -class PasswordStorage: - def __init__(self, name='BarksharkWeb'): - self.name = name - self.connection = None - self.collection = None - - - def __getitem__(self, key): - if (item := self.fetch(id=key).one()): - return item - - raise KeyError(f'No password with label: {key}') - - - def __setitem__(self, key, value): - if (item := self[key]): - item.update(**value) - - else: - self.insert(label=key, **value) - - - def __enter__(self): - self.connect() - return self - - - def __exit__(self, *args): - self.disconnect() - - - def connect(self): - if self.connection and self.collection: - return - - if not self.connection: - self.connection = secretstorage.dbus_init() - - if not secretstorage.check_service_availability(self.connection): - self.disconnect() - raise ConnectionError('Failed to connect to Secret Service server') - - return self.get_keyring() - - - def disconnect(self): - if self.connection: - self.connection.close() - - self.connection = None - self.collection = None - - - def get_keyring(self): - if not self.collection: - try: - self.collection = Collection(self.connection, f'/org/freedesktop/secrets/collection/{self.name}') - except ItemNotFoundException: - create_collection(self.connection, self.name) - self.collection = Collection(self.connection, f'/org/freedesktop/secrets/collection/{self.name}') - - try: - self.collection.ensure_not_locked() - except LockedException: - self.unlock() - - return self.collection - - - def unlock(self): - return self.collection.unlock() - - - def lock(self): - return self.collection.lock() - - - def fetch(self, *args, **kwargs): - data = parse_data(*args, **kwargs) - data.pop('password', None) - - if not any(data.values()): - rows = self.collection.get_all_items() - - else: - rows = self.collection.search_items(data) - - return PasswordResult(rows) - - - def insert(self, *args, label=None, **kwargs): - data = parse_data(*args, **kwargs) - data['id'] = random_str() - required = {key: data.get(key) for key in ['username', 'domain', 'password']} - - if None in required.values(): - raise ValueError(f'Forgot username, domain, or password: {json.dumps(required)}') - - username = data['username'] - password = data['password'] - password = data.pop('password') - - if (row := self.fetch(username, password).one()): - logging.verbose('Password already exists:', row.label) - return row - - if not label: - label = f'{username} @ {password}' - - row = self.collection.create_item(label, data, password.encode()) - return PasswordItem(row) - - - def update(self, username, domain, **kwargs): - row = self.fetch(username, domain).one() - row.update(**kwargs) - return row - - - def remove(self, *args, **kwargs): - for row in self.fetch(*args, **kwargs): - row.delete() - - -class PasswordResult: - def __init__(self, items): - self._items = items - - - def __iter__(self): - for item in self.all(): - yield item - - - def all(self): - for item in self._items: - yield PasswordItem(item) - - - def one(self): - for item in self.all(): - return item - - -class PasswordItem: - def __init__(self, item): - super().__init__() - - self._item = item - self._attrs = item.get_attributes() - - if not self._attrs.get('id'): - self.update({'id': random_str()}) - - - def __repr__(self): - return f'PasswordItem(id={self.id}, username={self.username}, domain={self.domain})' - - - def __getitem__(self, key): - if key not in pass_keys: - raise KeyError(key) - - return getattr(self, key) - - - def __setitem__(self, key, value): - if key not in pass_store_keys: - raise KeyError(key) - - self.update(**{key: value}) - - - @property - def created(self): - return datetime.fromtimestamp(self._item.get_created()) - - - @property - def modified(self): - return datetime.fromtimestamp(self._item.get_modified()) - - - @property - def label(self): - if not (label := self._item.get_label()): - return f'{self.username} @ {self.domain}' - - return label - - - @property - def id(self): - return self._attrs.get('id') - - - @property - def password(self): - return self._item.get_secret().decode() - - - @property - def username(self): - return self._attrs.get('username') - - - @property - def domain(self): - return self._attrs.get('domain') - - - @property - def url(self): - if (url := self._attrs.get('url')): - return Url(url) - - - @property - def note(self): - return self._attrs.get('note') - - - @password.setter - def password(self, value): - self.update(password=value) - - - @username.setter - def username(self, value): - self.update(username=value) - - - @domain.setter - def domain(self, value): - self.update(domain=value) - - - @url.setter - def url(self, value): - return self.update(url=str(value)) - - - @note.setter - def note(self, value): - return self.update(note=value) - - - def copy_password(self, timeout=60): - app = get_app() - app.set_clipboard_text(self.password) - - timer = TimeoutCallback(timeout, run_in_gui_thread, app.handle_clipboard_clear_password, self.password) - timer.start() - - - def copy_username(self): - get_app().set_clipboard_text(self.username) - - - def as_dict(self): - return DotDict( - id = self.id, - domain = self.domain, - username = self.username, - password = self.password, - url = self.url, - note = self.note, - created = self.created, - modified = self.modified - ) - - - def to_json(self, indent=None): - return self.as_dict().to_json(indent) - - - def update(self, data): - label = data.pop('label', None) - - if (password := data.pop('password', None)): - self._item.set_secret(password.encode()) - - if not len(data): - return - - new_data = parse_data(**self.as_dict()) - new_data.update(parse_data(**data)) - - self._item.set_attributes(new_data) - self._attrs = self._item.get_attributes() - - if label: - self._item.set_label(label) - - elif any(map(data.get, ['username', 'domain'])) and not self._item.get_label(): - self._item.set_label(self.label) - - - def delete(self): - self._item.delete() - - -passdb = PasswordStorage() -passdb.connect() diff --git a/barkshark_web/protocol/local.py b/barkshark_web/protocol/local.py index c4a86df..c02d020 100644 --- a/barkshark_web/protocol/local.py +++ b/barkshark_web/protocol/local.py @@ -9,7 +9,6 @@ from .functions import error, finish_request, finish_request_error, redirect from .. import var from ..functions import TimeoutCallback, get_app, run_in_gui_thread -from ..passwords import passdb class LocalRequest(ProtocolRequest): @@ -292,19 +291,19 @@ def history_clear(handler, request): def passwords_home(handler, request): domain = request.query.get('domain') - if (search_text := request.query.get('text')): - items = [] - rows = passdb.fetch(domain=domain) + with handler.db.session as s: + if (search_text := request.query.get('text')): + items = [] - for row in rows: - if True in fuzzy_string_match(search_text, row.domain, row.url, row.username, row.note, row.label): - items.append(row) + for row in s.fetch('passwords', domain=domain): + if True in fuzzy_string_match(search_text, row.domain, row.url, row.username, row.note, row.label): + items.append(row) - elif domain: - items = passdb.fetch(domain=domain).all() + elif domain: + items = s.fetch('passwords', domain=domain).all() - else: - items = passdb.fetch().all() + else: + items = s.fetch('passwords').all() return request.page('passwords', {'items': items}) @@ -314,60 +313,61 @@ def passwords_edit(handler, request, rowid=None): if not rowid: return request.page('password-edit', {'password': None}) - try: - item = passdb[rowid] - - except KeyError: - return request.error(f'Cannot find password: {rowid}', 404) + with handler.db.session as s: + if not (item := s.fetch('passwords', id=rowid).one()): + return request.error(f'Cannot find password: {rowid}', 404) return request.page('password-edit', {'password': item}) @Local.route('/passwords/update', '/passwords/update/{rowid}') def passwords_update(handler, request, rowid=None): - query = request.query + query = request.query.copy() + query.pop('redir', None) + + print(query.get('label')) if not query.get('label'): query['label'] = f'{query.username} @ {query.domain}' - if not rowid: - row = passdb.insert(**query) + print(query.get('label')) - return request.ok_or_redirect(f'Created new password: {row.label}') + for key, value in list(query.items()): + if isinstance(value, str) and not value: + del query[key] - try: - row = passdb[rowid] - row.update(query) + with handler.db.session as s: + if not rowid: + row = s.put_password(**query) + return request.ok_or_redirect(f'Created new password: {row.label}') - except KeyError: - return request.error(f'Cannot find password: {rowid}', 404) + if not (row := s.fetch('passwords', id=rowid).one()): + return request.error(f'Cannot find password: {rowid}', 404) - return request.ok_or_redirect(f'Updated password: {row.label}') + row = s.put_password_row(row, **query) + + return request.ok_or_redirect(f'updated password: {row.label}') @Local.route('/passwords/copy/{rowid}') def passwords_copy(handler, request, rowid): - try: - row = passdb[rowid] - - except KeyError: - return request.error(f'Cannot find password: {rowid}', 404) + with handler.db.session as s: + if not (row := s.fetch('passwords', id=rowid).one()): + return request.error(f'Cannot find password: {rowid}', 404) row.copy_password() - return request.ok_or_redirect(f'Copied password for 60 seconds') @Local.route('/passwords/delete/{rowid}') def passwords_update(handler, request, rowid): - try: - row = passdb[rowid] - label = row.label - row.delete() - return request.ok_or_redirect(f'Deleted password: {label}') + with handler.db.session as s: + try: + s.remove('passwords', id=rowid) + return request.ok_or_redirect(f'Deleted password: {label}') - except KeyError: - return request.error(f'Cannot find password: {rowid}', 404) + except KeyError: + return request.error(f'Cannot find password: {rowid}', 404) ### Preferences ### diff --git a/webextension b/webextension deleted file mode 160000 index 6c71f89..0000000 --- a/webextension +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6c71f895d8fc58a4cc58b593c8b7d2c509a7181e diff --git a/webextension/meson.build b/webextension/meson.build new file mode 100644 index 0000000..d36ec47 --- /dev/null +++ b/webextension/meson.build @@ -0,0 +1,10 @@ +project('BarksharkWebExtension', 'c', 'vala', version: '0.1') + +shared_library('extproxy', + 'webextension.vala', + dependencies: [ + dependency('webkit2gtk-web-extension-4.0'), + dependency('json-glib-1.0') + ], + install: true +) diff --git a/webextension/webextension.vala b/webextension/webextension.vala new file mode 100644 index 0000000..2d48b2a --- /dev/null +++ b/webextension/webextension.vala @@ -0,0 +1,121 @@ +private void handle_console_message_sent(WebKit.WebExtension extension, WebKit.ConsoleMessage message) { + var ext_message = new WebKit.UserMessage( + "console-message-sent", + new Variant("(ssi)", message.get_text(), message.get_level().to_string(), message.get_line()) + ); + + extension.send_message_to_context.begin(ext_message, null); +} + + +// private void handle_form_controls(WebKit.WebExtension extension, WebKit.WebPage page, GenericArray elements, WebKit.Frame frame) { +// var data = new Json.Builder(); +// data.begin_array(); +// +// elements.foreach((elem) => { +// data.begin_object(); +// +// data.set_member_name("tag");data.add_string_value(elem.tag_name); +// data.set_member_name("id");data.add_string_value(elem.id); +// data.set_member_name("name");data.add_string_value(elem.attributes.name); +// +// data.end_object(); +// }) +// +// data.end_array(); +// +// var ext_message = new WebKit.UserMessage( +// "context-menu", +// new Variant("", +// new Variant("()", ) +// ) +// ) +// } + + +// private async bool handle_send_request(WebKit.WebExtension extension, WebKit.URIRequest request, WebKit.URIResponse redirected_response) { +// var ext_message = new WebKit.UserMessage( +// "send-request", +// new Variant("(s)", request.get_uri()) +// ); +// +// var result = yield extension.send_message_to_context(ext_message, null); +// return result.get_parameters().get_boolean(); +// } + + +private void handle_form_submit(WebKit.WebExtension extension, WebKit.FormSubmissionStep stage, GenericArray keys, GenericArray values) { + var gen = new Json.Generator(); + var data = new Json.Builder(); + + data.begin_object(); + data.set_member_name("keys"); + data.begin_array(); + + keys.foreach((key) => { + data.add_string_value(key); + }); + + data.end_array(); + + data.set_member_name("values"); + data.begin_array(); + + values.foreach((value) => { + data.add_string_value(value); + }); + + data.end_array(); + data.end_object(); + + gen.set_root(data.get_root()); + + var ext_message = new WebKit.UserMessage( + "form-sent", + new Variant("(ss)", stage.to_string(), gen.to_data(null)) + ); + + extension.send_message_to_context.begin(ext_message, null); +} + + +private void handle_page_created(WebKit.WebExtension extension, WebKit.WebPage page) { + var ext_message = new WebKit.UserMessage( + "page-created", + new Variant("(t)", page.get_id()) + ); + + extension.send_message_to_context.begin(ext_message, null); + + page.console_message_sent.connect((message) => { + handle_console_message_sent(extension, message); + }); + +// page.context_menu.connect((ContextMenu menu, WebHitResult, hit_result) => { +// handle_context_menu(extension, menu, hit_result) +// }); + +// page.form_controls_associated_for_frame.connect((web_page, elements, frame) => { +// handle_form_controls(extension, web_page, elements, frame) +// }); + +// page.send_request.connect((request, redirected_response) => { +// return handle_send_request(extension, request, redirected_response); +// }); + + page.will_submit_form.connect((form, stage, source_frame, target_rame, field_names, field_values) => { + handle_form_submit(extension, stage, field_names, field_values); + }); +} + + +private void handle_message_recv(WebKit.WebExtension extension, WebKit.UserMessage message) { + print(message.get_name()); +} + + +[CCode (cname="G_MODULE_EXPORT webkit_web_extension_initialize", has_target = false)] +public static void webkit_web_extension_initialize(WebKit.WebExtension extension) { + extension.page_created.connect(handle_page_created); + extension.user_message_received.connect(handle_message_recv); +}