diff --git a/contrib/blinding.py b/contrib/blinding.py index 94f31857..e5a2c492 100644 --- a/contrib/blinding.py +++ b/contrib/blinding.py @@ -4,7 +4,7 @@ import nacl.hash from hashlib import blake2b -from pyonionreq import xed25519 +from session_util import xed25519 worked, trials = 0, 10000 diff --git a/install-uwsgi.md b/install-uwsgi.md index 1cb36f30..38fcc201 100644 --- a/install-uwsgi.md +++ b/install-uwsgi.md @@ -28,7 +28,7 @@ Python using: sudo curl -so /etc/apt/trusted.gpg.d/oxen.gpg https://deb.oxen.io/pub.gpg echo "deb https://deb.oxen.io $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/oxen.list sudo apt update -sudo apt install python3-{oxenmq,oxenc,pyonionreq,coloredlogs,uwsgidecorators,flask,cryptography,nacl,pil,protobuf,openssl,qrcode,better-profanity,sqlalchemy,sqlalchemy-utils} uwsgi-plugin-python3 +sudo apt install python3-{oxenmq,oxenc,session-util,coloredlogs,uwsgidecorators,flask,cryptography,nacl,pil,protobuf,openssl,qrcode,better-profanity,sqlalchemy,sqlalchemy-utils} uwsgi-plugin-python3 ``` If you want to use a postgresql database backend then you will also need the python3-psycopg2 diff --git a/setup.cfg b/setup.cfg index 46411cf5..8e71e335 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,7 @@ install_requires= better_profanity oxenmq oxenc - pyonionreq + session_util sqlalchemy setup_requires= tomli diff --git a/sogs/crypto.py b/sogs/crypto.py index c0f012e2..f8287887 100644 --- a/sogs/crypto.py +++ b/sogs/crypto.py @@ -18,7 +18,7 @@ import hmac import functools -import pyonionreq +from session_util import xed25519 if [int(v) for v in nacl.__version__.split('.')] < [1, 4]: raise ImportError("SOGS requires nacl v1.4.0+") @@ -64,9 +64,6 @@ def persist_privkey(): server_pubkey_hex = server_pubkey.encode(HexEncoder).decode('ascii') server_pubkey_base64 = server_pubkey.encode(Base64Encoder).decode('ascii') -_junk_parser = pyonionreq.junk.Parser(privkey=_privkey_bytes, pubkey=server_pubkey_bytes) -parse_junk = _junk_parser.parse_junk - def verify_sig_from_pk(data, sig, pk): return VerifyKey(pk).verify(data, sig) @@ -87,10 +84,6 @@ def server_encrypt(pk, data): return nonce + AESGCM(secret).encrypt(nonce, data, None) -xed25519_sign = pyonionreq.xed25519.sign -xed25519_verify = pyonionreq.xed25519.verify -xed25519_pubkey = pyonionreq.xed25519.pubkey - # AKA "k" for blinding crypto: blinding_factor = sodium.crypto_core_ed25519_scalar_reduce( blake2b(server_pubkey_bytes, digest_size=64) @@ -109,7 +102,7 @@ def compute_blinded_abs_key(x_pk: bytes, *, k: bytes = blinding_factor): k allows you to compute for an alternative blinding factor, but should normally be omitted. """ - A = xed25519_pubkey(x_pk) + A = xed25519.pubkey(x_pk) kA = sodium.crypto_scalarmult_ed25519_noclamp(k, A) if kA[31] & 0x80: diff --git a/sogs/db.py b/sogs/db.py index f078fa9c..f9481d27 100644 --- a/sogs/db.py +++ b/sogs/db.py @@ -4,6 +4,7 @@ import os import logging import importlib.resources +from contextlib import nullcontext import sqlalchemy from sqlalchemy.sql.expression import bindparam @@ -19,6 +20,34 @@ def get_conn(): return engine.connect() +# Begins a (potentially nested) transaction. Takes an optional connection; if omitted uses +# web.appdb. +def transaction(dbconn=None): + if dbconn is None: + from . import web + + dbconn = web.appdb + if dbconn.in_transaction(): + return dbconn.begin_nested() + else: + return dbconn.begin() + + +# Similar to transaction(), above, except that if we are already in a +# transaction this does nothing (unless transaction(), which uses savepoints +# effectively as sub-transactions). +def maybe_tx(dbconn=None): + if dbconn is None: + from . import web + + dbconn = web.appdb + + if dbconn.in_transaction(): + return nullcontext() + else: + return dbconn.begin() + + def query(query, *, dbconn=None, bind_expanding=None, **params): """Executes a query containing :param style placeholders (regardless of the actual underlying database placeholder style), binding them using the given params keyword arguments. @@ -51,17 +80,8 @@ def query(query, *, dbconn=None, bind_expanding=None, **params): if bind_expanding: q = q.bindparams(*(bindparam(c, expanding=True) for c in bind_expanding)) - return dbconn.execute(q, **params) - - -# Begins a (potentially nested) transaction. Takes an optional connection; if omitted uses -# web.appdb. -def transaction(dbconn=None): - if dbconn is None: - from . import web - - dbconn = web.appdb - return dbconn.begin_nested() + with maybe_tx(dbconn): + return dbconn.execute(q, params) have_returning = True diff --git a/sogs/migrations/__init__.py b/sogs/migrations/__init__.py index e1c797c2..1a5a5b65 100644 --- a/sogs/migrations/__init__.py +++ b/sogs/migrations/__init__.py @@ -53,14 +53,14 @@ def migrate(conn, *, check_only=False): import_hacks, ): changes = False - if check_only: - migration.migrate(conn, check_only=True) - else: - with db.transaction(conn): + with db.transaction(conn): + if check_only: + migration.migrate(conn, check_only=True) + else: changes = migration.migrate(conn, check_only=False) - if changes: - db.metadata.clear() - db.metadata.reflect(bind=db.engine, views=True) - any_changes = True + if changes: + db.metadata.clear() + db.metadata.reflect(bind=db.engine, views=True) + any_changes = True return any_changes diff --git a/sogs/migrations/file_message.py b/sogs/migrations/file_message.py index 14eac2e5..9556facc 100644 --- a/sogs/migrations/file_message.py +++ b/sogs/migrations/file_message.py @@ -1,5 +1,6 @@ from .exc import DatabaseUpgradeRequired import logging +from sqlalchemy import text def migrate(conn, *, check_only): @@ -23,11 +24,11 @@ def migrate(conn, *, check_only): # Prior versions of this script created the column referencing rooms(id) instead of # messages; we need to rewrite the schema to fix it. This schema updating feel janky, but # is the officially documented method (https://www.sqlite.org/lang_altertable.html) - conn.execute("UPDATE files SET message = NULL") - schema_ver = conn.execute("PRAGMA schema_version").first()[0] - files_sql = conn.execute( + conn.execute(text("UPDATE files SET message = NULL")) + schema_ver = conn.execute(text("PRAGMA schema_version")).first()[0] + files_sql = conn.execute(text( "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'files'" - ).first()[0] + )).first()[0] broken = 'message INTEGER REFERENCES rooms(id)' fixed = 'message INTEGER REFERENCES messages(id)' if broken not in files_sql: @@ -35,21 +36,21 @@ def migrate(conn, *, check_only): "Didn't find expected schema in files table; cannot proceed with upgrade!" ) files_sql = files_sql.replace(broken, fixed) - conn.execute("PRAGMA writable_schema=ON") - conn.execute( + conn.execute(text("PRAGMA writable_schema=ON")) + conn.execute(text( "UPDATE sqlite_master SET sql = ? WHERE type = 'table' AND name = 'files'", (files_sql,) - ) - conn.execute(f"PRAGMA schema_version={schema_ver+1}") - conn.execute("PRAGMA writable_schema=OFF") + )) + conn.execute(text(f"PRAGMA schema_version={schema_ver+1}")) + conn.execute(text("PRAGMA writable_schema=OFF")) elif db.engine.name == "sqlite": - conn.execute( + conn.execute(text( "ALTER TABLE files ADD COLUMN message INTEGER REFERENCES messages(id)" " ON DELETE SET NULL" - ) - conn.execute("CREATE INDEX files_message ON files(message)") - conn.execute("DROP TRIGGER IF EXISTS messages_after_delete") - conn.execute( + )) + conn.execute(text("CREATE INDEX files_message ON files(message)")) + conn.execute(text("DROP TRIGGER IF EXISTS messages_after_delete")) + conn.execute(text( """ CREATE TRIGGER messages_after_delete AFTER UPDATE OF data ON messages FOR EACH ROW WHEN NEW.data IS NULL AND OLD.data IS NOT NULL @@ -60,11 +61,11 @@ def migrate(conn, *, check_only): UPDATE files SET expiry = 0.0 WHERE message = OLD.id; END """ - ) - conn.execute("DROP TRIGGER IF EXISTS room_metadata_pinned_add") - conn.execute("DROP TRIGGER IF EXISTS room_metadata_pinned_update") - conn.execute("DROP TRIGGER IF EXISTS room_metadata_pinned_remove") - conn.execute( + )) + conn.execute(text("DROP TRIGGER IF EXISTS room_metadata_pinned_add")) + conn.execute(text("DROP TRIGGER IF EXISTS room_metadata_pinned_update")) + conn.execute(text("DROP TRIGGER IF EXISTS room_metadata_pinned_remove")) + conn.execute(text( """ CREATE TRIGGER room_metadata_pinned_add AFTER INSERT ON pinned_messages FOR EACH ROW @@ -73,8 +74,8 @@ def migrate(conn, *, check_only): UPDATE files SET expiry = NULL WHERE message = NEW.message; END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER room_metadata_pinned_update AFTER UPDATE ON pinned_messages FOR EACH ROW @@ -83,8 +84,8 @@ def migrate(conn, *, check_only): UPDATE files SET expiry = NULL WHERE message = NEW.message; END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER room_metadata_pinned_remove AFTER DELETE ON pinned_messages FOR EACH ROW @@ -93,10 +94,10 @@ def migrate(conn, *, check_only): UPDATE files SET expiry = uploaded + 15.0 * 86400.0 WHERE message = OLD.message; END """ - ) + )) else: - conn.execute( + conn.execute(text( """ ALTER TABLE files ADD COLUMN message BIGINT REFERENCES messages ON DELETE SET NULL; @@ -139,6 +140,6 @@ def migrate(conn, *, check_only): FOR EACH ROW EXECUTE PROCEDURE trigger_room_metadata_pinned_remove(); """ - ) + )) return True diff --git a/sogs/migrations/fix_info_update_triggers.py b/sogs/migrations/fix_info_update_triggers.py index 7cb02a06..9507b54a 100644 --- a/sogs/migrations/fix_info_update_triggers.py +++ b/sogs/migrations/fix_info_update_triggers.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -32,10 +33,10 @@ def migrate(conn, *, check_only): raise DatabaseUpgradeRequired("global hidden mod room triggers need to be recreated") if db.engine.name == "sqlite": - conn.execute("DROP TRIGGER IF EXISTS room_metadata_global_mods_insert") - conn.execute("DROP TRIGGER IF EXISTS room_metadata_global_mods_update") - conn.execute("DROP TRIGGER IF EXISTS room_metadata_global_mods_delete") - conn.execute( + conn.execute(text("DROP TRIGGER IF EXISTS room_metadata_global_mods_insert")) + conn.execute(text("DROP TRIGGER IF EXISTS room_metadata_global_mods_update")) + conn.execute(text("DROP TRIGGER IF EXISTS room_metadata_global_mods_delete")) + conn.execute(text( """ CREATE TRIGGER room_metadata_global_mods_insert AFTER INSERT ON users FOR EACH ROW WHEN (NEW.admin OR NEW.moderator) @@ -43,7 +44,7 @@ def migrate(conn, *, check_only): UPDATE rooms SET info_updates = info_updates + 1; -- WHERE everything! END """ - ) + )) conn.execute( """ CREATE TRIGGER room_metadata_global_mods_update AFTER UPDATE ON users @@ -53,7 +54,7 @@ def migrate(conn, *, check_only): END """ # noqa: E501 ) - conn.execute( + conn.execute(text( """ CREATE TRIGGER room_metadata_global_mods_delete AFTER DELETE ON users FOR EACH ROW WHEN (OLD.moderator OR OLD.admin) @@ -61,7 +62,7 @@ def migrate(conn, *, check_only): UPDATE rooms SET info_updates = info_updates + 1; -- WHERE everything! END """ - ) + )) else: # postgresql conn.execute( diff --git a/sogs/migrations/import_hacks.py b/sogs/migrations/import_hacks.py index 1556cb70..6a97ec12 100644 --- a/sogs/migrations/import_hacks.py +++ b/sogs/migrations/import_hacks.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -25,13 +26,13 @@ def migrate(conn, *, check_only): logging.warning("Dropping old_room_import_hacks temporary table") if check_only: raise DatabaseUpgradeRequired("old_room_import_hacks") - conn.execute('DROP TABLE old_room_import_hacks') + conn.execute(text('DROP TABLE old_room_import_hacks')) changed = True if 'file_id_hacks' in db.metadata.tables: # If the table exists but is empty (i.e. because all the attachments expired) then we should # drop it. - if not check_only and conn.execute("SELECT COUNT(*) FROM file_id_hacks").first()[0] == 0: + if not check_only and conn.execute(text("SELECT COUNT(*) FROM file_id_hacks")).first()[0] == 0: logging.warning("Dropping file_id_hacks old sogs import table (no longer required)") db.metadata.tables['file_id_hacks'].drop(db.engine) changed = True @@ -41,7 +42,7 @@ def migrate(conn, *, check_only): if 'room_import_hacks' in db.metadata.tables: rows = conn.execute( - "SELECT room, old_message_id_max, message_id_offset FROM room_import_hacks" + text("SELECT room, old_message_id_max, message_id_offset FROM room_import_hacks") ) for (room, id_max, offset) in rows: db.ROOM_IMPORT_HACKS[room] = (id_max, offset) @@ -60,15 +61,15 @@ def migrate(conn, *, check_only): # Annoyingly, sqlalchemy doesn't pick up foreign key actions when reflecting # sqlite (probably because sqlite doesn't enforce foreign keys by default), so # we have to pragma query the info ourself: - for fk in conn.execute('PRAGMA foreign_key_list("file_id_hacks")'): + for fk in conn.execute(text('PRAGMA foreign_key_list("file_id_hacks")')): if fk['from'] == 'room' and fk['on_delete'] != 'CASCADE': need_fix = True if need_fix: logging.warning("Replacing file_id_hacks to add cascading foreign key") if check_only: raise DatabaseUpgradeRequired("file_id_hacks") - conn.execute("ALTER TABLE file_id_hacks RENAME TO old_file_id_hacks") - conn.execute( + conn.execute(text("ALTER TABLE file_id_hacks RENAME TO old_file_id_hacks")) + conn.execute(text( """ CREATE TABLE file_id_hacks ( room INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE, @@ -77,13 +78,13 @@ def migrate(conn, *, check_only): PRIMARY KEY(room, old_file_id) ) """ - ) - conn.execute( + )) + conn.execute(text( """ INSERT INTO file_id_hacks SELECT room, old_file_id, file FROM old_file_id_hacks """ - ) + )) changed = True @@ -92,15 +93,15 @@ def migrate(conn, *, check_only): # Annoyingly, sqlalchemy doesn't pick up foreign key actions when reflecting # sqlite (probably because sqlite doesn't enforce foreign keys by default), so # we have to pragma query the info ourself: - for fk in conn.execute('PRAGMA foreign_key_list("room_import_hacks")'): + for fk in conn.execute(text('PRAGMA foreign_key_list("room_import_hacks")')): if fk['from'] == 'room' and fk['on_delete'] != 'CASCADE': need_fix = True if need_fix: logging.warning("Replacing room_import_hacks to add cascading foreign key") if check_only: raise DatabaseUpgradeRequired("room_import_hacks") - conn.execute("ALTER TABLE room_import_hacks RENAME TO old_room_import_hacks") - conn.execute( + conn.execute(text("ALTER TABLE room_import_hacks RENAME TO old_room_import_hacks")) + conn.execute(text( """ CREATE TABLE room_import_hacks ( room INTEGER PRIMARY KEY NOT NULL REFERENCES rooms(id) ON DELETE CASCADE, @@ -108,15 +109,15 @@ def migrate(conn, *, check_only): message_id_offset INTEGER NOT NULL ) """ - ) - conn.execute( + )) + conn.execute(text( """ INSERT INTO room_import_hacks SELECT room, old_message_id_max, message_id_offset FROM old_room_import_hacks """ - ) - conn.execute('DROP TABLE old_room_import_hacks') + )) + conn.execute(text('DROP TABLE old_room_import_hacks')) changed = True @@ -133,21 +134,21 @@ def migrate(conn, *, check_only): if check_only: raise DatabaseUpgradeRequired("v0.1.x import hacks tables") if fix_fid: - conn.execute( + conn.execute(text( """ ALTER TABLE file_id_hacks DROP CONSTRAINT file_id_hacks_room_fkey; ALTER TABLE file_id_hacks ADD CONSTRAINT file_id_hacks_room_fkey FOREIGN KEY (room) REFERENCES rooms(id); """ - ) + )) if fix_room: - conn.execute( + conn.execute(text( """ ALTER TABLE room_import_hacks DROP CONSTRAINT room_import_hacks_room_fkey; ALTER TABLE room_import_hacks ADD CONSTRAINT room_import_hacks_room_fkey FOREIGN KEY (room) REFERENCES rooms(id); """ - ) + )) changed = True return changed diff --git a/sogs/migrations/message_views.py b/sogs/migrations/message_views.py index 3ae03d41..f255fdd7 100644 --- a/sogs/migrations/message_views.py +++ b/sogs/migrations/message_views.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -32,12 +33,12 @@ def migrate(conn, *, check_only): if check_only: raise DatabaseUpgradeRequired("message views need to be recreated") - conn.execute("DROP VIEW IF EXISTS message_metadata") - conn.execute("DROP VIEW IF EXISTS message_details") + conn.execute(text("DROP VIEW IF EXISTS message_metadata")) + conn.execute(text("DROP VIEW IF EXISTS message_details")) if db.engine.name == "sqlite": - conn.execute("DROP TRIGGER IF EXISTS message_details_deleter") - conn.execute( + conn.execute(text("DROP TRIGGER IF EXISTS message_details_deleter")) + conn.execute(text( """ CREATE VIEW message_details AS SELECT messages.*, uposter.session_id, uwhisper.session_id AS whisper_to @@ -45,8 +46,8 @@ def migrate(conn, *, check_only): JOIN users uposter ON messages."user" = uposter.id LEFT JOIN users uwhisper ON messages.whisper = uwhisper.id """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER message_details_deleter INSTEAD OF DELETE ON message_details FOR EACH ROW WHEN OLD.data IS NOT NULL @@ -57,8 +58,8 @@ def migrate(conn, *, check_only): SELECT id FROM reactions WHERE message = OLD.id); END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE VIEW message_metadata AS SELECT id, room, "user", session_id, posted, edited, seqno, seqno_data, seqno_reactions, seqno_creation, @@ -66,10 +67,10 @@ def migrate(conn, *, check_only): length(data) AS data_unpadded, data_size, length(signature) as signature_length FROM message_details """ # noqa: E501 - ) + )) else: # postgresql - conn.execute( + conn.execute(text( """ -- Effectively the same as `messages` except that it also includes the `session_id` from the users -- table of the user who posted it, and the session id of the whisper recipient (as `whisper_to`) if @@ -105,6 +106,6 @@ def migrate(conn, *, check_only): length(data) AS data_unpadded, data_size, length(signature) as signature_length FROM message_details; """ # noqa: E501 - ) + )) return True diff --git a/sogs/migrations/new_columns.py b/sogs/migrations/new_columns.py index 6518c6e3..e556d922 100644 --- a/sogs/migrations/new_columns.py +++ b/sogs/migrations/new_columns.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -27,7 +28,7 @@ def migrate(conn, *, check_only): logging.warning(f"DB migration: Adding new column {table}.{name}") if check_only: raise DatabaseUpgradeRequired(f"new column {table}.{name}") - conn.execute(f"ALTER TABLE {table} ADD COLUMN {name} {definition}") + conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {name} {definition}")) added = True return added diff --git a/sogs/migrations/new_tables.py b/sogs/migrations/new_tables.py index e855d956..ccb574e3 100644 --- a/sogs/migrations/new_tables.py +++ b/sogs/migrations/new_tables.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text # { table_name => { 'sqlite': ['query1', 'query2'], 'pgsql': "query1; query2" } } @@ -90,9 +91,9 @@ def migrate(conn, *, check_only): if db.engine.name == 'sqlite': for query in v['sqlite']: - conn.execute(query) + conn.execute(text(query)) else: - conn.execute(v['pgsql']) + conn.execute(text(v['pgsql'])) added = True diff --git a/sogs/migrations/reactions.py b/sogs/migrations/reactions.py index acff1aa7..5ff05bc5 100644 --- a/sogs/migrations/reactions.py +++ b/sogs/migrations/reactions.py @@ -1,5 +1,6 @@ from .exc import DatabaseUpgradeRequired import logging +from sqlalchemy import text def migrate(conn, *, check_only): @@ -15,14 +16,14 @@ def migrate(conn, *, check_only): if db.engine.name == "sqlite": if 'seqno_data' not in db.metadata.tables['messages'].c: - conn.execute("ALTER TABLE messages ADD COLUMN seqno_data INTEGER NOT NULL DEFAULT 0") + conn.execute(text("ALTER TABLE messages ADD COLUMN seqno_data INTEGER NOT NULL DEFAULT 0")) conn.execute( "ALTER TABLE messages ADD COLUMN seqno_reactions INTEGER NOT NULL DEFAULT 0" ) - conn.execute("UPDATE messages SET seqno_data = seqno") + conn.execute(text("UPDATE messages SET seqno_data = seqno")) - conn.execute("DROP TRIGGER IF EXISTS messages_insert_counter") - conn.execute( + conn.execute(text("DROP TRIGGER IF EXISTS messages_insert_counter")) + conn.execute(text( """ CREATE TRIGGER messages_insert_counter AFTER INSERT ON messages FOR EACH ROW @@ -32,9 +33,9 @@ def migrate(conn, *, check_only): WHERE id = NEW.id; END """ - ) - conn.execute("DROP TRIGGER IF EXISTS messages_insert_history") - conn.execute( + )) + conn.execute(text("DROP TRIGGER IF EXISTS messages_insert_history")) + conn.execute(text( """ CREATE TRIGGER messages_insert_history AFTER UPDATE OF data ON messages FOR EACH ROW WHEN NEW.data IS NOT OLD.data @@ -47,8 +48,8 @@ def migrate(conn, *, check_only): WHERE id = NEW.id; END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER messages_seqno_updater_ins AFTER INSERT ON messages FOR EACH ROW @@ -56,8 +57,8 @@ def migrate(conn, *, check_only): UPDATE messages SET seqno = max(seqno_data, seqno_reactions) WHERE id = NEW.id; END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER messages_seqno_updater_upd AFTER UPDATE OF seqno_data, seqno_reactions ON messages FOR EACH ROW @@ -65,10 +66,10 @@ def migrate(conn, *, check_only): UPDATE messages SET seqno = max(seqno_data, seqno_reactions) WHERE id = NEW.id; END """ - ) - conn.execute("DROP TABLE IF EXISTS reactions") - conn.execute("DROP TABLE IF EXISTS user_reactions") - conn.execute( + )) + conn.execute(text("DROP TABLE IF EXISTS reactions")) + conn.execute(text("DROP TABLE IF EXISTS user_reactions")) + conn.execute(text( """ CREATE TABLE reactions ( id INTEGER NOT NULL PRIMARY KEY, @@ -76,8 +77,8 @@ def migrate(conn, *, check_only): reaction TEXT NOT NULL ) """ - ) - conn.execute("CREATE UNIQUE INDEX reactions_message ON reactions (message, reaction)") + )) + conn.execute(text("CREATE UNIQUE INDEX reactions_message ON reactions (message, reaction)")) conn.execute( """ CREATE TABLE user_reactions ( @@ -88,15 +89,15 @@ def migrate(conn, *, check_only): ) """ ) - conn.execute("CREATE INDEX user_reactions_at ON user_reactions(reaction, at)") - conn.execute("DROP VIEW IF EXISTS message_reactions") - conn.execute( + conn.execute(text("CREATE INDEX user_reactions_at ON user_reactions(reaction, at)")) + conn.execute(text("DROP VIEW IF EXISTS message_reactions")) + conn.execute(text( """ CREATE VIEW message_reactions AS SELECT reactions.*, user_reactions.user, user_reactions.at FROM reactions JOIN user_reactions ON user_reactions.reaction = reactions.id """ - ) + )) conn.execute( """ CREATE TRIGGER message_reactions_insert INSTEAD OF INSERT ON message_reactions @@ -110,14 +111,14 @@ def migrate(conn, *, check_only): END """ ) - conn.execute( + conn.execute(text( """ CREATE VIEW first_reactors AS SELECT *, rank() OVER (PARTITION BY reaction ORDER BY at) AS _order FROM user_reactions """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER reactions_no_update BEFORE UPDATE ON reactions FOR EACH ROW @@ -125,7 +126,7 @@ def migrate(conn, *, check_only): SELECT RAISE(ABORT, 'reactions is not UPDATEable'); END """ - ) + )) conn.execute( """ CREATE TRIGGER user_reactions_insert_seqno AFTER INSERT ON user_reactions @@ -141,7 +142,7 @@ def migrate(conn, *, check_only): END """ # noqa: E501 ) - conn.execute( + conn.execute(text( """ CREATE TRIGGER user_reactions_no_update BEFORE UPDATE ON user_reactions FOR EACH ROW @@ -149,7 +150,7 @@ def migrate(conn, *, check_only): SELECT RAISE(ABORT, 'user_reactions is not UPDATEable'); END """ - ) + )) conn.execute( """ CREATE TRIGGER reactions_delete_seqno BEFORE DELETE ON user_reactions @@ -165,7 +166,7 @@ def migrate(conn, *, check_only): END """ # noqa: E501 ) - conn.execute( + conn.execute(text( """ CREATE TRIGGER reactions_cleanup_empty AFTER DELETE ON user_reactions FOR EACH ROW @@ -174,20 +175,20 @@ def migrate(conn, *, check_only): AND NOT EXISTS(SELECT * FROM user_reactions WHERE reaction = reactions.id); END """ - ) + )) else: # postgresql if 'seqno_data' not in db.metadata.tables['messages'].c: - conn.execute( + conn.execute(text( """ ALTER TABLE messages ADD COLUMN seqno_data INTEGER NOT NULL DEFAULT 0; ALTER TABLE messages ADD COLUMN seqno_reactions INTEGER NOT NULL DEFAULT 0; UPDATE messages SET seqno_data = seqno; """ - ) + )) - conn.execute( + conn.execute(text( """ CREATE OR REPLACE FUNCTION increment_room_sequence(room_id BIGINT) @@ -344,6 +345,6 @@ def migrate(conn, *, check_only): EXECUTE PROCEDURE trigger_reactions_clear_empty(); """ - ) + )) return True diff --git a/sogs/migrations/room_accessible.py b/sogs/migrations/room_accessible.py index 26f6ee3a..167d85ae 100644 --- a/sogs/migrations/room_accessible.py +++ b/sogs/migrations/room_accessible.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -14,15 +15,15 @@ def migrate(conn, *, check_only): if check_only: raise DatabaseUpgradeRequired("Add accessible room permission columns") - conn.execute("ALTER TABLE rooms ADD COLUMN accessible BOOLEAN NOT NULL DEFAULT TRUE") - conn.execute("ALTER TABLE user_permission_overrides ADD COLUMN accessible BOOLEAN") + conn.execute(text("ALTER TABLE rooms ADD COLUMN accessible BOOLEAN NOT NULL DEFAULT TRUE")) + conn.execute(text("ALTER TABLE user_permission_overrides ADD COLUMN accessible BOOLEAN")) # Gets recreated in the user_permissions migration: - conn.execute("DROP VIEW IF EXISTS user_permissions") + conn.execute(text("DROP VIEW IF EXISTS user_permissions")) if db.engine.name == "sqlite": - conn.execute("DROP TRIGGER IF EXISTS user_perms_empty_cleanup") - conn.execute( + conn.execute(text("DROP TRIGGER IF EXISTS user_perms_empty_cleanup")) + conn.execute(text( """ CREATE TRIGGER user_perms_empty_cleanup AFTER UPDATE ON user_permission_overrides FOR EACH ROW WHEN NOT (NEW.banned OR NEW.moderator OR NEW.admin) @@ -31,10 +32,10 @@ def migrate(conn, *, check_only): DELETE from user_permission_overrides WHERE room = NEW.room AND user = NEW.user; END """ - ) + )) else: - conn.execute( + conn.execute(text( """ DROP TRIGGER IF EXISTS user_perms_empty_cleanup ON user_permission_overrides; @@ -43,6 +44,6 @@ def migrate(conn, *, check_only): AND COALESCE(NEW.accessible, NEW.read, NEW.write, NEW.upload) IS NULL) EXECUTE PROCEDURE trigger_user_perms_empty_cleanup(); """ - ) + )) return True diff --git a/sogs/migrations/room_moderators.py b/sogs/migrations/room_moderators.py index 25f116b5..3fac253b 100644 --- a/sogs/migrations/room_moderators.py +++ b/sogs/migrations/room_moderators.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -20,7 +21,7 @@ def migrate(conn, *, check_only): raise DatabaseUpgradeRequired("Create room_moderators view") if db.engine.name == "sqlite": - conn.execute( + conn.execute(text( """ CREATE VIEW room_moderators AS SELECT session_id, mods.* FROM ( @@ -54,9 +55,9 @@ def migrate(conn, *, check_only): ) m GROUP BY "user", room ) mods JOIN users on "user" = users.id """ - ) + )) else: # postgres - conn.execute( + conn.execute(text( """ CREATE VIEW room_moderators AS SELECT session_id, mods.* FROM ( @@ -90,13 +91,13 @@ def migrate(conn, *, check_only): ) m GROUP BY "user", room ) mods JOIN users on "user" = users.id """ - ) + )) - conn.execute("DROP VIEW IF EXISTS user_permissions") - conn.execute("DROP INDEX IF EXISTS user_permission_overrides_public_mods") - conn.execute( + conn.execute(text("DROP VIEW IF EXISTS user_permissions")) + conn.execute(text("DROP INDEX IF EXISTS user_permission_overrides_public_mods")) + conn.execute(text( "CREATE INDEX IF NOT EXISTS user_permission_overrides_mods " "ON user_permission_overrides(room) WHERE moderator" - ) + )) return True diff --git a/sogs/migrations/seqno_creation.py b/sogs/migrations/seqno_creation.py index a1810032..01aa81ae 100644 --- a/sogs/migrations/seqno_creation.py +++ b/sogs/migrations/seqno_creation.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -19,9 +20,9 @@ def migrate(conn, *, check_only): logging.warning("Adding messages.seqno_creation column") if db.engine.name == 'sqlite': - conn.execute("ALTER TABLE messages ADD COLUMN seqno_creation INTEGER NOT NULL DEFAULT 0") - conn.execute("DROP TRIGGER IF EXISTS messages_insert_counter") - conn.execute( + conn.execute(text("ALTER TABLE messages ADD COLUMN seqno_creation INTEGER NOT NULL DEFAULT 0")) + conn.execute(text("DROP TRIGGER IF EXISTS messages_insert_counter")) + conn.execute(text( """ CREATE TRIGGER messages_insert_counter AFTER INSERT ON messages FOR EACH ROW @@ -31,9 +32,9 @@ def migrate(conn, *, check_only): UPDATE messages SET seqno_creation = seqno_data WHERE id = NEW.id; END """ # noqa: E501 - ) + )) else: # postgresql - conn.execute( + conn.execute(text( """ ALTER TABLE messages ADD COLUMN seqno_creation BIGINT NOT NULL DEFAULT 0; @@ -49,10 +50,10 @@ def migrate(conn, *, check_only): CREATE TRIGGER messages_insert_counter AFTER INSERT ON messages FOR EACH ROW EXECUTE PROCEDURE trigger_messages_insert_counter(); """ - ) + )) # Drop these to be recreated (with the no column) in the message_views migration. - conn.execute("DROP VIEW IF EXISTS message_metadata") - conn.execute("DROP VIEW IF EXISTS message_details") + conn.execute(text("DROP VIEW IF EXISTS message_metadata")) + conn.execute(text("DROP VIEW IF EXISTS message_details")) return True diff --git a/sogs/migrations/seqno_etc.py b/sogs/migrations/seqno_etc.py index 2650374a..05b56179 100644 --- a/sogs/migrations/seqno_etc.py +++ b/sogs/migrations/seqno_etc.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -31,9 +32,9 @@ def migrate(conn, *, check_only): # anyway, so we just recreate the whole thing (along with triggers which we also need to # update/fix) logging.warning("Recreating pinned_messages table") - conn.execute("DROP TABLE pinned_messages") + conn.execute(text("DROP TABLE pinned_messages")) if db.engine.name == 'sqlite': - conn.execute( + conn.execute(text( """ CREATE TABLE pinned_messages ( room INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE, @@ -43,8 +44,8 @@ def migrate(conn, *, check_only): PRIMARY KEY(room, message) ) """ # noqa: E501 - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER room_metadata_pinned_add AFTER INSERT ON pinned_messages FOR EACH ROW @@ -52,8 +53,8 @@ def migrate(conn, *, check_only): UPDATE rooms SET info_updates = info_updates + 1 WHERE id = NEW.room; END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER room_metadata_pinned_update AFTER UPDATE ON pinned_messages FOR EACH ROW @@ -61,8 +62,8 @@ def migrate(conn, *, check_only): UPDATE rooms SET info_updates = info_updates + 1 WHERE id = NEW.room; END """ - ) - conn.execute( + )) + conn.execute(text( """ CREATE TRIGGER room_metadata_pinned_remove AFTER DELETE ON pinned_messages FOR EACH ROW @@ -70,11 +71,11 @@ def migrate(conn, *, check_only): UPDATE rooms SET info_updates = info_updates + 1 WHERE id = OLD.room; END """ - ) + )) else: # postgresql logging.warning("Recreating pinned_messages table") - conn.execute( + conn.execute(text( """ CREATE TABLE pinned_messages ( room BIGINT NOT NULL REFERENCES rooms ON DELETE CASCADE, @@ -93,21 +94,21 @@ def migrate(conn, *, check_only): FOR EACH ROW EXECUTE PROCEDURE trigger_room_metadata_info_update_old(); """ - ) + )) logging.warning("Applying message_sequence renames") - conn.execute("ALTER TABLE rooms RENAME COLUMN updates TO message_sequence") + conn.execute(text("ALTER TABLE rooms RENAME COLUMN updates TO message_sequence")) # The message_views migration will create these for us, and we need to drop them because: # 1) postgresql doesn't rename the view's output columns to match the new table column # 2) sqlite breaks if attempting the rename a column that is referenced in a view-of-a-view - conn.execute("DROP VIEW message_metadata") - conn.execute("DROP VIEW message_details") + conn.execute(text("DROP VIEW message_metadata")) + conn.execute(text("DROP VIEW message_details")) - conn.execute("ALTER TABLE messages RENAME COLUMN updated TO seqno") + conn.execute(text("ALTER TABLE messages RENAME COLUMN updated TO seqno")) # Gets recreated in the user_permissions migration: logging.warning("Dropping user_permissions view") - conn.execute("DROP VIEW IF EXISTS user_permissions") + conn.execute(text("DROP VIEW IF EXISTS user_permissions")) return True diff --git a/sogs/migrations/user_perm_futures.py b/sogs/migrations/user_perm_futures.py index e1b36aff..2427e8f6 100644 --- a/sogs/migrations/user_perm_futures.py +++ b/sogs/migrations/user_perm_futures.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -20,8 +21,8 @@ def migrate(conn, *, check_only): if db.engine.name == 'sqlite': # Under sqlite we have to drop and recreate the whole thing. (Since we didn't have a # release out that was using futures yet, we don't bother trying to migrate data). - conn.execute("DROP TABLE user_permission_futures") - conn.execute( + conn.execute(text("DROP TABLE user_permission_futures")) + conn.execute(text( """ CREATE TABLE user_permission_futures ( room INTEGER NOT NULL REFERENCES rooms ON DELETE CASCADE, @@ -32,15 +33,15 @@ def migrate(conn, *, check_only): upload BOOLEAN /* Set this value @ at, if non-null */ ) """ - ) - conn.execute("CREATE INDEX user_permission_futures_at ON user_permission_futures(at)") - conn.execute( + )) + conn.execute(text("CREATE INDEX user_permission_futures_at ON user_permission_futures(at)")) + conn.execute(text( """ CREATE INDEX user_permission_futures_room_user ON user_permission_futures(room, user) """ - ) + )) - conn.execute( + conn.execute(text( """ CREATE TABLE user_ban_futures ( room INTEGER REFERENCES rooms ON DELETE CASCADE, @@ -49,12 +50,12 @@ def migrate(conn, *, check_only): banned BOOLEAN NOT NULL /* if true then ban at `at`, if false then unban */ ); """ - ) - conn.execute("CREATE INDEX user_ban_futures_at ON user_ban_futures(at)") - conn.execute("CREATE INDEX user_ban_futures_room_user ON user_ban_futures(room, user)") + )) + conn.execute(text("CREATE INDEX user_ban_futures_at ON user_ban_futures(at)")) + conn.execute(text("CREATE INDEX user_ban_futures_room_user ON user_ban_futures(room, user)")) else: # postgresql - conn.execute( + conn.execute(text( """ CREATE TABLE user_ban_futures ( room INTEGER REFERENCES rooms ON DELETE CASCADE, @@ -73,6 +74,6 @@ def migrate(conn, *, check_only): ALTER TABLE user_permission_futures DROP CONSTRAINT IF EXISTS user_permission_futures_pkey; ALTER TABLE user_permission_futures DROP COLUMN banned; """ - ) + )) return True diff --git a/sogs/migrations/user_permissions.py b/sogs/migrations/user_permissions.py index a3c9319c..425cadc4 100644 --- a/sogs/migrations/user_permissions.py +++ b/sogs/migrations/user_permissions.py @@ -1,5 +1,6 @@ import logging from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): @@ -17,7 +18,7 @@ def migrate(conn, *, check_only): if check_only: raise DatabaseUpgradeRequired("Recreate user_permissions view") - conn.execute( + conn.execute(text( """ CREATE VIEW user_permissions AS SELECT @@ -46,6 +47,6 @@ def migrate(conn, *, check_only): users CROSS JOIN rooms LEFT OUTER JOIN user_permission_overrides ON (users.id = user_permission_overrides."user" AND rooms.id = user_permission_overrides.room) """ # noqa E501 - ) + )) return True diff --git a/sogs/migrations/v_0_1_x.py b/sogs/migrations/v_0_1_x.py index b59c1ef3..3c6de627 100644 --- a/sogs/migrations/v_0_1_x.py +++ b/sogs/migrations/v_0_1_x.py @@ -5,10 +5,11 @@ import logging import time from .exc import DatabaseUpgradeRequired +from sqlalchemy import text def migrate(conn, *, check_only): - n_rooms = conn.execute("SELECT COUNT(*) FROM rooms").first()[0] + n_rooms = conn.execute(text("SELECT COUNT(*) FROM rooms")).first()[0] # Migration from a v0.1.x database: if n_rooms > 0 or not os.path.exists("database.db"): diff --git a/sogs/model/file.py b/sogs/model/file.py index fb55030a..99a38199 100644 --- a/sogs/model/file.py +++ b/sogs/model/file.py @@ -46,18 +46,15 @@ def __init__(self, row=None, *, id=None): self.filename, self.path, ) = ( - row[c] - for c in ( - 'id', - 'room', - 'uploader', - 'message', - 'size', - 'uploaded', - 'expiry', - 'filename', - 'path', - ) + row.id, + row.room, + row.uploader, + row.message, + row.size, + row.uploaded, + row.expiry, + row.filename, + row.path, ) self._room = None self._uploader = None diff --git a/sogs/model/message.py b/sogs/model/message.py index a40bca52..9c65fea6 100644 --- a/sogs/model/message.py +++ b/sogs/model/message.py @@ -92,28 +92,28 @@ def sent(user, since=None, limit=None): @property def id(self): - return self._row["id"] + return self._row.id @property def posted_at(self): - return self._row["posted_at"] + return self._row.posted_at @property def expires_at(self): - return self._row["expiry"] + return self._row.expiry @property def data(self): - return self._row['body'] + return self._row.body @property def sender(self): if not hasattr(self, "_sender"): - self._sender = User(id=self._row['sender'], autovivify=False) + self._sender = User(id=self._row.sender, autovivify=False) return self._sender @property def recipient(self): if not hasattr(self, "_recip"): - self._recip = User(id=self._row['recipient'], autovivify=False) + self._recip = User(id=self._row.recipient, autovivify=False) return self._recip diff --git a/sogs/model/room.py b/sogs/model/room.py index 7217b116..545f13d2 100644 --- a/sogs/model/room.py +++ b/sogs/model/room.py @@ -116,21 +116,18 @@ def _refresh(self, *, id=None, token=None, row=None, perms=False): self.info_updates, self.active_users, ) = ( - row[c] - for c in ( - 'id', - 'token', - 'name', - 'description', - 'image', - 'created', - 'message_sequence', - 'info_updates', - 'active_users', - ) + row.id, + row.token, + row.name, + row.description, + row.image, + row.created, + row.message_sequence, + row.info_updates, + row.active_users, ) self._default_read, self._default_accessible, self._default_write, self._default_upload = ( - bool(row[c]) for c in ('read', 'accessible', 'write', 'upload') + bool(x) for x in (row.read, row.accessible, row.write, row.upload) ) if ( @@ -697,27 +694,28 @@ def get_messages_for( user=user.id if user else None, limit=limit, ): - if sequence and row['seqno_reactions'] > sequence >= row['seqno_data']: + if sequence and row.seqno_reactions > sequence >= row.seqno_data: # This is a reaction-only update, so we only want to include the reaction info # (added later) but not the full details. - msgs.append({x: row[x] for x in ('id', 'seqno')}) + msgs.append({'id': row.id, 'seqno': row.seqno }) continue - msg = {x: row[x] for x in ('id', 'session_id', 'posted', 'seqno')} - data = row['data'] + msg = {'id': row.id, 'session_id': row.session_id, 'posted': row.posted, 'seqno': row.seqno} + + data = row.data if data is None: msg['data'] = None msg['deleted'] = True else: - msg['data'] = utils.add_session_message_padding(data, row['data_size']) - msg['signature'] = row['signature'] - if row['edited'] is not None: - msg['edited'] = row['edited'] - if row['whisper_to'] is not None or row['whisper_mods']: + msg['data'] = utils.add_session_message_padding(data, row.data_size) + msg['signature'] = row.signature + if row.edited is not None: + msg['edited'] = row.edited + if row.whisper_to is not None or row.whisper_mods: msg['whisper'] = True - msg['whisper_mods'] = row['whisper_mods'] - if row['whisper_to'] is not None: - msg['whisper_to'] = row['whisper_to'] + msg['whisper_mods'] = row.whisper_mods + if row.whisper_to is not None: + msg['whisper_to'] = row.whisper_to msgs.append(msg) if reactions: @@ -1920,7 +1918,7 @@ def get_file(self, file_id: int): ).first() if not row: return - if row['expiry'] is None or row['expiry'] > time.time(): + if row.expiry is None or row.expiry > time.time(): return File(row) def upload_file( @@ -2140,10 +2138,10 @@ def permissions(self): r=self.id, ): data = dict() - for k in row.keys(): - if row[k] is not None and k not in ('session_id', 'room', 'user'): - data[k] = bool(row[k]) - ret[row['session_id']] = data + for k, v in row.mappings().items(): + if v is not None and k not in ('session_id', 'room', 'user'): + data[k] = bool(v) + ret[row.session_id] = data return ret def user_permissions(self, user): @@ -2160,7 +2158,7 @@ def user_permissions(self, user): if not row: return {} return { - k: bool(row[k]) for k in row.keys() if k not in ('room', 'user') and row[k] is not None + k: bool(v) for k, v in row.mappings().items() if k not in ('room', 'user') and v is not None } @property @@ -2185,13 +2183,13 @@ def future_permissions(self): r=self.id, ): data = dict() - for k in row.keys(): + for k, v in row.mappings().items(): if k == 'user': continue if k in ('at', 'session_id'): - data[k] = row[k] - elif row[k] is not None: - data[k] = bool(row[k]) + data[k] = v + elif v is not None: + data[k] = bool(v) ret.append(data) return ret @@ -2216,8 +2214,8 @@ def user_future_permissions(self, user): u=user.id, r=self.id, ): - result.append({k: bool(row[k]) for k in row.keys() if k != 'at' and row[k] is not None}) - result[-1]['at'] = row['at'] + result.append({k: bool(v) for k, v in row.mappings().items if k != 'at' and v is not None}) + result[-1]['at'] = row[0] return result diff --git a/sogs/model/user.py b/sogs/model/user.py index e1b452e4..d76dd588 100644 --- a/sogs/model/user.py +++ b/sogs/model/user.py @@ -113,10 +113,10 @@ def _refresh( raise NoSuchUser(session_id if session_id is not None else id) self.id, self.session_id, self.created, self.last_active = ( - row[c] for c in ('id', 'session_id', 'created', 'last_active') + row.id, row.session_id, row.created, row.last_active ) self.banned, self.global_moderator, self.global_admin, self.visible_mod = ( - bool(row[c]) for c in ('banned', 'moderator', 'admin', 'visible_mod') + bool(v) for v in (row.banned, row.moderator, row.admin, row.visible_mod) ) def _import_blinded(self, session_id): @@ -152,28 +152,28 @@ def _import_blinded(self, session_id): "users", "id", sid=session_id, - cr=to_import["created"], - la=to_import["last_active"], - ban=to_import["banned"], - mod=to_import["moderator"], - admin=to_import["admin"], - vis=to_import["visible_mod"], + cr=to_import.created, + la=to_import.last_active, + ban=to_import.banned, + mod=to_import.moderator, + admin=to_import.admin, + vis=to_import.visible_mod, ) # If we have any global ban/admin/mod then clear them (because we've just set up the # global ban/mod/admin permissions for the blinded id in the query above). query( "UPDATE users SET banned = FALSE, admin = FALSE, moderator = FALSE WHERE id = :u", - u=to_import["id"], + u=to_import.id, ) for t in ("user_permission_overrides", "user_permission_futures", "user_ban_futures"): query( f'UPDATE {t} SET "user" = :new WHERE "user" = :old', - new=row["id"], - old=to_import["id"], + new=row.id, + old=to_import.id, ) - query('DELETE FROM needs_blinding WHERE "user" = :u', u=to_import["id"]) + query('DELETE FROM needs_blinding WHERE "user" = :u', u=to_import.id) return row @@ -333,7 +333,7 @@ def verify(self, *, message: bytes, sig: bytes): """verify signature signed by this session id return True if the signature is valid otherwise return False """ - pk = crypto.xed25519_pubkey(bytes.fromhex(self.session_id[2:])) + pk = crypto.xed25519.pubkey(bytes.fromhex(self.session_id[2:])) return crypto.verify_sig_from_pk(message, sig, pk) def find_blinded(self): diff --git a/sogs/routes/auth.py b/sogs/routes/auth.py index dbbf5abf..d4b1a449 100644 --- a/sogs/routes/auth.py +++ b/sogs/routes/auth.py @@ -338,7 +338,7 @@ def handle_http_auth(): to_verify = ( crypto.server_pubkey_bytes + nonce - + ts_str.encode() + + str(ts_str).encode() + request.method.encode() + request.path.encode() ) diff --git a/sogs/routes/onion_request.py b/sogs/routes/onion_request.py index bdf53a3d..4c2fb416 100644 --- a/sogs/routes/onion_request.py +++ b/sogs/routes/onion_request.py @@ -6,6 +6,8 @@ from .subrequest import make_subrequest +from session_util.onionreq import OnionReqParser + onion_request = Blueprint('onion_request', __name__) @@ -245,7 +247,7 @@ def handle_v4_onionreq_plaintext(body): def decrypt_onionreq(): try: - return crypto.parse_junk(request.data) + return OnionReqParser(crypto.server_pubkey_bytes, crypto._privkey_bytes, request.data) except Exception as e: app.logger.warning("Failed to decrypt onion request: {}".format(e)) abort(http.BAD_REQUEST) @@ -262,8 +264,8 @@ def handle_v3_onion_request(): Deprecated in favour of /v4/. """ - junk = decrypt_onionreq() - return utils.encode_base64(junk.transformReply(handle_v3_onionreq_plaintext(junk.payload))) + parser = decrypt_onionreq() + return utils.encode_base64(parser.encrypt_reply(handle_v3_onionreq_plaintext(parser.payload))) @onion_request.post("/oxen/v4/lsrpc") @@ -287,7 +289,7 @@ def handle_v4_onion_request(): # The parse_junk here takes care of decoding and decrypting this according to the fields *meant # for us* in the json (which include things like the encryption type and ephemeral key): try: - junk = crypto.parse_junk(request.data) + parser = decrypt_onionreq() except RuntimeError as e: app.logger.warning("Failed to decrypt onion request: {}".format(e)) abort(http.BAD_REQUEST) @@ -295,5 +297,5 @@ def handle_v4_onion_request(): # On the way back out we re-encrypt via the junk parser (which uses the ephemeral key and # enc_type that were specified in the outer request). We then return that encrypted binary # payload as-is back to the client which bounces its way through the SN path back to the client. - response = handle_v4_onionreq_plaintext(junk.payload) - return junk.transformReply(response) + response = handle_v4_onionreq_plaintext(parser.payload) + return parser.encrypt_reply(response) diff --git a/sogs/routes/subrequest.py b/sogs/routes/subrequest.py index d856a5fd..d149ee73 100644 --- a/sogs/routes/subrequest.py +++ b/sogs/routes/subrequest.py @@ -85,7 +85,7 @@ def make_subrequest( "PATH_INFO": monkey_path, "QUERY_STRING": query_string, "CONTENT_TYPE": content_type, - "CONTENT_LENGTH": content_length, + "CONTENT_LENGTH": str(content_length), **http_headers, 'wsgi.input': body_input, 'flask._preserve_context': False,