ticketfrei/db.py

320 lines
12 KiB
Python
Raw Permalink Normal View History

2018-03-28 15:36:35 +00:00
from config import config
2018-03-22 01:23:31 +00:00
import jwt
2018-03-24 15:26:35 +00:00
import logging
2020-08-07 10:39:04 +00:00
from os import urandom, system
2018-03-28 15:36:35 +00:00
from pylibscrypt import scrypt_mcf
2018-03-22 01:23:31 +00:00
import sqlite3
from time import sleep, time
2018-03-22 01:23:31 +00:00
2023-06-30 11:22:34 +00:00
logger = logging.getLogger("main")
2018-03-24 15:26:35 +00:00
2018-03-22 01:23:31 +00:00
class DB(object):
def __init__(self, dbfile):
2018-03-22 01:23:31 +00:00
self.conn = sqlite3.connect(dbfile)
self.cur = self.conn.cursor()
self.create()
2018-03-22 01:23:31 +00:00
2018-03-28 15:36:35 +00:00
def execute(self, *args, **kwargs):
return self.cur.execute(*args, **kwargs)
def commit(self):
start_time = time()
while 1:
try:
self.conn.commit()
break
2023-06-22 06:14:30 +00:00
except sqlite3.OperationalError as error:
# another thread may be writing, give it a chance to finish
2023-06-22 06:14:30 +00:00
sleep(0.5)
logger.exception()
if time() - start_time > 5:
# if it takes this long, something is wrong
system("rcctl restart frontend_daemon")
2023-06-22 06:14:30 +00:00
logger.warning("frontend_daemon is getting restarted")
self.conn.commit()
2018-03-28 15:36:35 +00:00
def close(self):
self.conn.close()
2018-03-22 01:23:31 +00:00
def create(self):
# init db
self.cur.executescript('''
CREATE TABLE IF NOT EXISTS user (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
passhash TEXT,
enabled INTEGER DEFAULT 1
);
2018-03-28 15:36:35 +00:00
CREATE TABLE IF NOT EXISTS email (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
2018-03-28 15:36:35 +00:00
user_id INTEGER,
email TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
2018-03-28 15:36:35 +00:00
CREATE TABLE IF NOT EXISTS triggerpatterns (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
patterns TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
2018-03-28 15:36:35 +00:00
CREATE TABLE IF NOT EXISTS badwords (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
words TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS mastodon_instances (
2018-03-28 15:36:35 +00:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
instance TEXT,
client_id TEXT,
client_secret TEXT
);
CREATE TABLE IF NOT EXISTS mastodon_accounts (
2018-03-28 15:36:35 +00:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
access_token TEXT,
instance_id INTEGER,
active INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id),
FOREIGN KEY(instance_id) REFERENCES mastodon_instances(id)
);
CREATE TABLE IF NOT EXISTS seen_toots (
2018-09-09 18:28:13 +00:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
2018-09-24 18:54:57 +00:00
toot_uri TEXT,
2018-09-24 19:00:55 +00:00
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS seen_telegrams (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
tg_id INTEGER,
2018-09-09 15:51:07 +00:00
FOREIGN KEY(user_id) REFERENCES user(id)
);
2018-03-28 15:36:35 +00:00
CREATE TABLE IF NOT EXISTS twitter_request_tokens (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
request_token TEXT,
2018-04-14 15:19:20 +00:00
request_token_secret TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
2018-03-28 15:36:35 +00:00
CREATE TABLE IF NOT EXISTS twitter_accounts (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
client_id TEXT,
client_secret TEXT,
active INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id)
);
2018-06-23 22:00:48 +00:00
CREATE TABLE IF NOT EXISTS telegram_accounts (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
apikey TEXT,
active INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS seen_tweets (
2018-09-24 20:10:23 +00:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
2018-09-24 20:14:17 +00:00
tweet_id INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id)
2018-03-28 15:36:35 +00:00
);
2018-04-15 09:58:19 +00:00
CREATE TABLE IF NOT EXISTS seen_dms (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
twitter_accounts_id INTEGER,
message_id TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
FOREIGN KEY(twitter_accounts_id)
REFERENCES twitter_accounts(id)
);
2018-05-25 00:38:27 +00:00
CREATE TABLE IF NOT EXISTS telegram_subscribers (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
subscriber_id INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id),
UNIQUE(user_id, subscriber_id) ON CONFLICT IGNORE
);
2018-03-28 22:13:00 +00:00
CREATE TABLE IF NOT EXISTS mailinglist (
2018-03-28 15:36:35 +00:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
email TEXT,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS seen_mail (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
mail_date REAL,
2018-03-28 15:36:35 +00:00
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS twitter_last_request (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
date INTEGER,
FOREIGN KEY(user_id) REFERENCES user(id)
);
CREATE TABLE IF NOT EXISTS cities (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER,
city TEXT,
markdown TEXT,
mail_md TEXT,
masto_link TEXT,
twit_link TEXT,
FOREIGN KEY(user_id) REFERENCES user(id),
UNIQUE(user_id, city) ON CONFLICT IGNORE
);
CREATE TABLE IF NOT EXISTS secret (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
secret BLOB
);
''')
2018-03-22 01:23:31 +00:00
def get_secret(self):
"""
At __init__(), the db needs a secret. It tries to fetch it from the db,
and if it fails, it generates a new one.
:return:
"""
# select only the newest secret. should be only one row anyway.
self.execute("SELECT secret FROM secret ORDER BY id DESC LIMIT 1")
try:
return self.cur.fetchone()[0]
except TypeError:
new_secret = urandom(32)
self.execute("INSERT INTO secret (secret) VALUES (?);",
(new_secret, ))
self.commit()
return new_secret
2018-03-28 15:36:35 +00:00
def user_token(self, email, password):
"""
This function is called by the register confirmation process. It wants
to write an email to the email table and a passhash to the user table.
:param email: a string with an E-Mail address.
:param password: a string with a passhash.
:return:
"""
2018-03-22 01:23:31 +00:00
return jwt.encode({
'email': email,
'passhash': scrypt_mcf(
password.encode('utf-8')
).decode('ascii')
2019-01-11 12:23:37 +00:00
}, self.get_secret()).decode('ascii')
2018-03-22 01:23:31 +00:00
def mail_subscription_token(self, email, city):
"""
This function is called by the mail subscription process. It wants
to write an email to the mailinglist table.
:param email: string
:param city: string
:return: a token with an encoded json dict { email: x, city: y }
"""
token = jwt.encode({
'email': email,
'city': city
2019-01-11 12:23:37 +00:00
}, self.get_secret()).decode('ascii')
return token
def confirm_subscription(self, token):
2019-01-11 12:23:37 +00:00
json = jwt.decode(token, self.get_secret())
return json['email'], json['city']
def confirm(self, token, city):
2018-03-28 22:12:19 +00:00
from user import User
2018-03-28 15:36:35 +00:00
try:
2019-01-11 12:23:37 +00:00
json = jwt.decode(token, self.get_secret())
2018-03-28 15:36:35 +00:00
except jwt.DecodeError:
return None # invalid token
if 'passhash' in json.keys():
# create user
2018-03-28 22:59:13 +00:00
self.execute("INSERT INTO user (passhash) VALUES(?);",
2018-03-28 15:36:35 +00:00
(json['passhash'], ))
uid = self.cur.lastrowid
2018-09-09 18:22:41 +00:00
default_triggerpatterns = """kontroll?e
konti
db
vgn
vag
zivil
sicherheit
uniform
station
bus
bahn
tram
linie
nuernberg
nürnberg
s\d
2018-09-09 18:22:41 +00:00
u\d\d?"""
self.execute("""INSERT INTO triggerpatterns (user_id, patterns)
VALUES(?, ?); """, (uid, default_triggerpatterns))
self.execute("INSERT INTO badwords (user_id, words) VALUES(?, ?);",
2018-05-25 14:15:44 +00:00
(uid, "bastard"))
2018-03-28 15:36:35 +00:00
else:
uid = json['uid']
with open("/etc/aliases", "a+") as f:
2018-10-07 21:01:14 +00:00
f.write(city + ": " + config["mail"]["mbox_user"] + "\n")
2020-07-15 19:01:29 +00:00
try:
os.system("newaliases")
except:
logger.exception()
2018-03-28 15:36:35 +00:00
self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);",
(uid, json['email']))
2018-06-23 22:00:48 +00:00
self.execute("""INSERT INTO telegram_accounts (user_id, apikey,
active) VALUES(?, ?, ?);""", (uid, "", 1))
2018-10-07 20:16:00 +00:00
self.execute("INSERT INTO seen_telegrams (user_id, tg_id) VALUES (?, ?);", (uid, 0))
self.execute("INSERT INTO seen_mail (user_id, mail_date) VALUES (?, ?);", (uid, 0))
self.execute("INSERT INTO seen_tweets (user_id, tweet_id) VALUES (?, ?)", (uid, 0))
self.execute("INSERT INTO twitter_last_request (user_id, date) VALUES (?, ?)", (uid, 0))
2018-03-28 15:36:35 +00:00
self.commit()
user = User(uid)
user.set_city(city)
return user
2018-03-22 01:23:31 +00:00
def by_email(self, email):
2018-03-28 22:12:19 +00:00
from user import User
2018-03-28 15:36:35 +00:00
self.execute("SELECT user_id FROM email WHERE email=?;", (email, ))
try:
uid, = self.cur.fetchone()
except TypeError:
2018-03-22 01:23:31 +00:00
return None
2018-03-28 15:36:35 +00:00
return User(uid)
2018-03-22 01:23:31 +00:00
def by_city(self, city):
from user import User
self.execute("SELECT user_id FROM cities WHERE city=?", (city, ))
try:
uid, = self.cur.fetchone()
except TypeError:
return None
return User(uid)
def user_facing_properties(self, city):
self.execute("""SELECT city, markdown, mail_md, masto_link, twit_link
FROM cities
WHERE city=?;""", (city, ))
try:
city, markdown, mail_md, masto_link, twit_link = self.cur.fetchone()
return dict(city=city,
markdown=markdown,
mail_md=mail_md,
masto_link=masto_link,
twit_link=twit_link,
mailinglist=city + "@" + config["web"]["host"])
except TypeError:
return None
2018-03-28 15:36:35 +00:00
@property
def active_users(self):
2018-03-28 22:12:19 +00:00
from user import User
2018-03-28 15:36:35 +00:00
self.execute("SELECT id FROM user WHERE enabled=1;")
return [User(uid) for uid, in self.cur.fetchall()]
2018-03-22 01:23:31 +00:00
2018-03-28 15:36:35 +00:00
db = DB(config['database']['db_path'])