class SqliteSessionInterface(FlaskSessionInterface): """STORE SESSION DATA IN SQLITE :param db: Sqlite database :param table: The table name you want to use. :param use_signer: Whether to sign the session id cookie or not. """ @override def __init__(self, flask_app, db, cookie, table="sessions"): global SINGLTON if SINGLTON: Log.error("Can only handle one session manager at a time") SINGLTON = self if is_data(db): self.db = Sqlite(db) else: self.db = db self.table = table self.cookie = cookie self.cookie.max_lifetime = parse(self.cookie.max_lifetime) self.cookie.inactive_lifetime = parse(self.cookie.inactive_lifetime) if not self.db.about(self.table): self.setup() Thread.run("session monitor", self.monitor) def create_session(self, session): session.session_id = generate_sid() session.permanent = True session.expires = (Date.now() + self.cookie.max_lifetime).unix def monitor(self, please_stop): while not please_stop: # Delete expired session try: with self.db.transaction() as t: t.execute("DELETE FROM " + quote_column(self.table) + SQL_WHERE + sql_lt(expires=Date.now().unix)) except Exception as e: Log.warning("problem with session expires", cause=e) (please_stop | Till(seconds=60)).wait() def setup(self): with self.db.transaction() as t: t.execute( sql_create( self.table, { "session_id": "TEXT PRIMARY KEY", "data": "TEXT", "last_used": "NUMBER", "expires": "NUMBER", }, )) def cookie_data(self, session): return { "session_id": session.session_id, "expires": session.expires, "inactive_lifetime": self.cookie.inactive_lifetime.seconds, } def update_session(self, session_id, props): """ UPDATE GIVEN SESSION WITH PROPERTIES :param session_id: :param props: :return: """ now = Date.now().unix session = self.get_session(session_id) for k, v in props.items(): session[k] = v session.last_used = now record = { "session_id": session_id, "data": value2json(session), "expires": session.expires, "last_used": session.last_used, } with self.db.transaction() as t: t.execute(SQL_UPDATE + quote_column(self.table) + SQL_SET + sql_list(sql_eq(**{k: v}) for k, v in record.items()) + SQL_WHERE + sql_eq(session_id=session_id)) def get_session(self, session_id): now = Date.now().unix result = self.db.query( sql_query({ "from": self.table, "where": { "eq": { "session_id": session_id } } })) saved_record = first(Data(zip(result.header, r)) for r in result.data) if not saved_record or saved_record.expires <= now: return Data() session = json2value(saved_record.data) DEBUG and Log.note("record from db {{session}}", session=saved_record) return session @register_thread def open_session(self, app, request): session_id = request.headers.get("Authorization") DEBUG and Log.note("got session_id {{session|quote}}", session=session_id) if not session_id: return Data() return self.get_session(session_id) @register_thread def save_session(self, app, session, response): if not session or not session.keys(): return if not session.session_id: session.session_id = generate_sid() session.permanent = True DEBUG and Log.note("save session {{session}}", session=session) now = Date.now().unix session_id = session.session_id result = self.db.query( sql_query({ "from": self.table, "where": { "eq": { "session_id": session_id } } })) saved_record = first(Data(zip(result.header, r)) for r in result.data) expires = min(session.expires, now + self.cookie.inactive_lifetime.seconds) if saved_record: DEBUG and Log.note("found session {{session}}", session=saved_record) saved_record.data = value2json(session) saved_record.expires = expires saved_record.last_used = now with self.db.transaction() as t: t.execute("UPDATE " + quote_column(self.table) + SQL_SET + sql_list( sql_eq(**{k: v}) for k, v in saved_record.items()) + SQL_WHERE + sql_eq(session_id=session_id)) else: new_record = { "session_id": session_id, "data": value2json(session), "expires": expires, "last_used": now, } DEBUG and Log.note("new record for db {{session}}", session=new_record) with self.db.transaction() as t: t.execute(sql_insert(self.table, new_record))
class Permissions: @override def __init__(self, db, kwargs): if is_data(db): self.db = Sqlite(db) elif isinstance(db, Sqlite): self.db = db else: Log.error("Bad db parameter") if not self.db.about(PERMISSION_TABLE): self.setup() self.next_id = id_generator(self.db) def setup(self): with self.db.transaction() as t: t.execute(sql_create(VERSION_TABLE, {"version": "TEXT"})) t.execute(sql_insert(VERSION_TABLE, {"version": "1.0"})) t.execute( sql_create( GROUP_TABLE, { "_id": "LONG PRIMARY KEY", "name": "TEXT", "group": "TEXT", "email": "TEXT", "issuer": "TEXT", "email_verified": "INTEGER", "description": "TEXT", "owner": "LONG", }, )) t.execute( sql_insert( GROUP_TABLE, [ { "_id": 1, "name": "root", "email": "*****@*****.**", "description": "access for security system", }, { "_id": 11, "group": "public", "description": "everyone with confirmed email", "owner": 1, }, { "_id": 12, "group": "mozillians", "description": "people that mozilla authentication has recongized as mozillian", "owner": 1, }, { "_id": 13, "group": "moz-employee", "description": "people that mozilla authentication has recongized as employee", "owner": 1, }, ], )) t.execute( sql_create( RESOURCE_TABLE, { "_id": "LONG PRIMARY KEY", "table": "TEXT", "operation": "TEXT", "owner": "LONG", }, )) t.execute( sql_insert( RESOURCE_TABLE, [ CREATE_TABLE, { "_id": 101, "table": ".", "operation": "update", "owner": 1 }, { "_id": 102, "table": ".", "operation": "from", "owner": 1 }, ], )) t.execute( sql_create( PERMISSION_TABLE, { "user": "******", "resource": "LONG", "owner": "LONG" }, )) t.execute( sql_insert( PERMISSION_TABLE, [ { "user": 12, "resource": 11, "owner": 1 }, { "user": 13, "resource": 11, "owner": 1 }, { "user": 13, "resource": 12, "owner": 1 }, { "user": 1, "resource": 100, "owner": 1 }, { "user": 1, "resource": 101, "owner": 1 }, { "user": 1, "resource": 102, "owner": 1 }, ], )) def create_table_resource(self, table_name, owner): """ CREATE A TABLE, CREATE RESOURCES FOR OPERATIONS, ENSURE CREATOR HAS CONTROL OVER TABLE :param table_name: Create resources for given table :param owner: assign this user as owner :return: """ new_resources = wrap([{ "table": table_name, "operation": op, "owner": 1 } for op in TABLE_OPERATIONS]) self._insert(RESOURCE_TABLE, new_resources) with self.db.transaction() as t: t.execute( sql_insert(PERMISSION_TABLE, [{ "user": owner._id, "resource": r._id, "owner": ROOT_USER._id } for r in new_resources])) def get_or_create_user(self, details): details = wrap(details) issuer = details.sub or details.issuer email = details.email email_verified = details.email_verified if not email: Log.error("Expecting id_token to have claims.email propert") result = self.db.query( sql_query({ "select": ["_id", "email", "issuer"], "from": GROUP_TABLE, "where": { "eq": { "email": email, "issuer": issuer } }, })) if result.data: user = Data(zip(result.header, first(result.data))) user.email_verified = email_verified return user new_user = wrap({ "email": email, "issuer": issuer, "email_verified": email_verified, "owner": ROOT_USER._id }) self._insert(GROUP_TABLE, new_user) return new_user def get_resource(self, table, operation): result = self.db.query( sql_query({ "select": "_id", "from": RESOURCE_TABLE, "where": { "eq": { "table": table, "operation": operation } }, })) if not result.data: Log.error("Expecting to find a resource") return Data(zip(result.header, first(result.data))) def add_permission(self, user, resource, owner): """ :param user: :param resource: :param owner: :return: """ user = wrap(user) resource = wrap(resource) owner = wrap(owner) # DOES owner HAVE ACCESS TO resource? if not self.verify_allowance(owner, resource): Log.error("not allowed to assign resource") # DOES THIS PERMISSION EXIST ALREADY allowance = self.verify_allowance(user, resource) if allowance: if any(r.owner == owner for r in allowance): Log.error("already allowed via {{allowance}}", allowance=allowance) # ALREADY ALLOWED, BUT MULTIPLE PATHS MAY BE OK with self.db.transaction() as t: t.execute( sql_insert(PERMISSION_TABLE, { "user": user._id, "resource": resource._id, "owner": owner._id })) def verify_allowance(self, user, resource): """ VERIFY IF user CAN ACCESS resource :param user: :param resource: :return: ALLOWANCE CHAIN """ user = wrap(user) resource = wrap(resource) resources = self.db.query( sql_query({ "select": ["resource", "owner"], "from": PERMISSION_TABLE, "where": { "eq": { "user": user._id } }, })) for r in resources.data: record = Data(zip(resources.header, r)) if record.resource == resource._id: if record.owner == ROOT_USER._id: return FlatList(vals=[{ "resource": resource, "user": user, "owner": ROOT_USER }]) else: cascade = self.verify_allowance( wrap({"_id": record.owner}), resource) if cascade: cascade.append({ "resource": resource, "user": user, "owner": record.owner }) return cascade else: group = record.resource cascade = self.verify_allowance(wrap({"_id": group}), resource) if cascade: cascade.append({ "group": group, "user": user, "owner": record.owner }) return cascade return [] def find_resource(self, table, operation): result = self.db.query( sql_query({ "from": RESOURCE_TABLE, "where": { "eq": { "table": table, "operation": operation } } })) return first(Data(zip(result.header, r)) for r in result.data) def _insert(self, table, records): records = listwrap(records) keys = {"_id"} for r in records: keys.update(r.keys()) if r._id == None: r._id = self.next_id() with self.db.transaction() as t: t.execute(sql_insert(table, records))