store passwords in database
This commit is contained in:
parent
3ff8e4886f
commit
b6a085b538
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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:]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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";
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
|
@ -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 ###
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 6c71f895d8fc58a4cc58b593c8b7d2c509a7181e
|
10
webextension/meson.build
Normal file
10
webextension/meson.build
Normal file
|
@ -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
|
||||
)
|
121
webextension/webextension.vala
Normal file
121
webextension/webextension.vala
Normal file
|
@ -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<WebKit.DOM.Element> 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<string> keys, GenericArray<string> 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);
|
||||
}
|
Reference in a new issue