Files
jobtrackingapp/docs/redesign/phase-9/rd21-remediation/diagnostics/readonly-db-proof.py
T

107 lines
5.5 KiB
Python

"""Read only the explicitly named synthetic owner's committed SQLite rows."""
import json
from contextlib import closing
from pathlib import Path
import sqlite3
import sys
import tempfile
CHILDREN = ("CareerExperiences", "CareerEducations", "CareerSkills", "CareerProjects",
"CareerCertifications", "CareerLanguages")
def connect_readonly(filename):
path = Path(filename).resolve(strict=True)
connection = sqlite3.connect(path.as_uri() + "?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
return connection
def snapshot(connection, owner, job_id=None):
def rows(sql, parameters):
return [dict(row) for row in connection.execute(sql, parameters)]
identities = rows('SELECT "Id", "ProfileCvText", "ProfileCvStructureJson", '
'"CurrentCvProfileVersion" FROM "AspNetUsers" WHERE "Id" = ?', (owner,))
if len(identities) != 1:
raise ValueError("Expected exactly one explicitly owned synthetic Identity row")
careers = rows('SELECT * FROM "CareerProfiles" WHERE "OwnerUserId" = ?', (owner,))
if len(careers) > 1:
raise ValueError("More than one canonical Career Profile for owner")
result = {
"identity": identities[0],
"career": careers[0] if careers else None,
"careerHistory": rows('SELECT * FROM "CareerProfileVersions" WHERE "OwnerUserId" = ? ORDER BY "Version", "Id"', (owner,)),
"children": {table: rows(f'SELECT * FROM "{table}" WHERE "OwnerUserId" = ? ORDER BY "SortOrder", "Id"', (owner,)) for table in CHILDREN},
"job": None, "opportunity": None, "events": [],
}
if job_id is not None:
jobs = rows('SELECT * FROM "JobApplications" WHERE "OwnerUserId" = ? AND "Id" = ?', (owner, job_id))
if len(jobs) != 1:
raise ValueError("Expected exactly one explicitly owned synthetic job")
result["job"] = jobs[0]
opportunities = rows('SELECT * FROM "Jobs" WHERE "OwnerUserId" = ? AND "Id" = ?', (owner, jobs[0]["JobId"]))
if len(opportunities) != 1:
raise ValueError("Expected the job's explicitly owned opportunity")
result["opportunity"] = opportunities[0]
result["events"] = rows('SELECT e.* FROM "JobEvents" e JOIN "JobApplications" j ON e."JobApplicationId" = j."Id" '
'WHERE j."OwnerUserId" = ? AND j."Id" = ? ORDER BY e."Id"', (owner, job_id))
return result
def self_test():
with tempfile.TemporaryDirectory(prefix="rd21-readonly-proof-") as directory:
filename = Path(directory) / "jobtracker.db"
with closing(sqlite3.connect(filename)) as writer, writer:
writer.executescript('''
CREATE TABLE AspNetUsers (Id TEXT, ProfileCvText TEXT, ProfileCvStructureJson TEXT, CurrentCvProfileVersion INTEGER);
INSERT INTO AspNetUsers VALUES ('owner', 'raw', '{}', 1), ('other', 'private', '{}', 1);
CREATE TABLE CareerProfiles (Id INTEGER, OwnerUserId TEXT, Version INTEGER, ProfileJson TEXT);
INSERT INTO CareerProfiles VALUES (1, 'owner', 1, '{}'), (2, 'other', 1, '{}');
CREATE TABLE CareerProfileVersions (Id INTEGER, OwnerUserId TEXT, Version INTEGER);
INSERT INTO CareerProfileVersions VALUES (1, 'owner', 1), (2, 'other', 1);
CREATE TABLE JobApplications (Id INTEGER, OwnerUserId TEXT, JobId INTEGER, Version INTEGER);
INSERT INTO JobApplications VALUES (1, 'owner', 10, 2), (2, 'other', 20, 3);
CREATE TABLE Jobs (Id INTEGER, OwnerUserId TEXT);
INSERT INTO Jobs VALUES (10, 'owner'), (20, 'other');
CREATE TABLE JobEvents (Id INTEGER, JobApplicationId INTEGER);
INSERT INTO JobEvents VALUES (1, 1), (2, 2);
''')
for table in CHILDREN:
writer.execute(f'CREATE TABLE "{table}" (Id INTEGER, OwnerUserId TEXT, SortOrder INTEGER)')
writer.executemany(f'INSERT INTO "{table}" VALUES (?, ?, ?)', [(1, "owner", 0), (2, "other", 0)])
with closing(connect_readonly(filename)) as reader:
before = snapshot(reader, "owner", 1)
assert before["career"]["Version"] == 1
assert before["job"]["Version"] == 2
assert before["opportunity"]["Id"] == 10
assert before["events"] == [{"Id": 1, "JobApplicationId": 1}]
assert all(len(rows) == 1 for rows in before["children"].values())
assert "private" not in json.dumps(before)
try:
reader.execute('UPDATE CareerProfiles SET Version = 99')
except sqlite3.OperationalError as error:
assert "readonly" in str(error).lower()
else:
raise AssertionError("Read-only URI allowed mutation")
assert snapshot(reader, "owner", 1) == before
try:
snapshot(reader, "owner", 2)
except ValueError:
pass
else:
raise AssertionError("Cross-owner job was returned")
print("readonly-db-proof self-test passed")
if __name__ == "__main__":
if sys.argv[1:] == ["--self-test"]:
self_test()
else:
if len(sys.argv) not in (3, 4):
raise SystemExit("Usage: readonly-db-proof.py DATABASE OWNER_ID [JOB_ID] | --self-test")
with closing(connect_readonly(sys.argv[1])) as connection:
connection.execute("BEGIN")
print(json.dumps(snapshot(connection, sys.argv[2], int(sys.argv[3]) if len(sys.argv) == 4 else None)))