Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contrib/blinding.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import nacl.hash
from hashlib import blake2b

from pyonionreq import xed25519
from session_util import xed25519

worked, trials = 0, 10000

Expand Down
2 changes: 1 addition & 1 deletion install-uwsgi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ install_requires=
better_profanity
oxenmq
oxenc
pyonionreq
session_util
sqlalchemy
setup_requires=
tomli
11 changes: 2 additions & 9 deletions sogs/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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+")
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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:
Expand Down
42 changes: 31 additions & 11 deletions sogs/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import logging
import importlib.resources
from contextlib import nullcontext
import sqlalchemy
from sqlalchemy.sql.expression import bindparam

Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions sogs/migrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
53 changes: 27 additions & 26 deletions sogs/migrations/file_message.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .exc import DatabaseUpgradeRequired
import logging
from sqlalchemy import text


def migrate(conn, *, check_only):
Expand All @@ -23,33 +24,33 @@ 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:
raise RuntimeError(
"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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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;

Expand Down Expand Up @@ -139,6 +140,6 @@ def migrate(conn, *, check_only):
FOR EACH ROW
EXECUTE PROCEDURE trigger_room_metadata_pinned_remove();
"""
)
))

return True
15 changes: 8 additions & 7 deletions sogs/migrations/fix_info_update_triggers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from .exc import DatabaseUpgradeRequired
from sqlalchemy import text


def migrate(conn, *, check_only):
Expand Down Expand Up @@ -32,18 +33,18 @@ 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)
BEGIN
UPDATE rooms SET info_updates = info_updates + 1; -- WHERE everything!
END
"""
)
))
conn.execute(
"""
CREATE TRIGGER room_metadata_global_mods_update AFTER UPDATE ON users
Expand All @@ -53,15 +54,15 @@ 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)
BEGIN
UPDATE rooms SET info_updates = info_updates + 1; -- WHERE everything!
END
"""
)
))

else: # postgresql
conn.execute(
Expand Down
Loading