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 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 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 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)) response.set_cookie(app.session_cookie_name, session_id, expires=expires)
def _gen_ids(): while True: with db.transaction() as t: top_id = first( first( t.query( sql_query({ "select": "next_id", "from": VERSION_TABLE })).data)) max_id = top_id + 1000 t.execute(SQL_UPDATE + quote_column(VERSION_TABLE) + SQL_SET + sql_eq(next_id=max_id)) while top_id < max_id: yield top_id top_id += 1
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 device_status(self, path=None): """ AUTOMATION CAN CALL THIS ENDPOINT TO FIND OUT THE LOGIN STATUS RESPOND WITH {"ok":true} WHEN USER HAS LOGGED IN, AND user IS ASSOCIATED WITH SESSION """ now = Date.now().unix if not session.session_id: return Response('{"try_again":false, "status":"no session id"}', status=401) request_body = request.get_data().strip() signed = json2value(request_body.decode("utf8")) command = rsa_crypto.verify(signed, session.public_key) time_sent = parse(command.timestamp) if not (now - LEEWAY <= time_sent < now + LEEWAY): return Response( '{"try_again":false, "status":"timestamp is not recent"}', status=401) if session.expires < now: return Response( '{"try_again":false, "status":"session is too old"}', status=401) if session.user: session.public_key = None return Response('{"try_again":false, "status":"verified"}', status=200) state_info = self.device.db.query( sql_query({ "select": "session_id", "from": self.device.table, "where": { "eq": { "state": session.state } }, })) if not state_info.data: return Response( '{"try_again":false, "status":"State has been lost"}', status=401) return Response('{"try_again":true, "status":"still waiting"}', status=200)
def _load_from_database(self): # FIND ALL TABLES result = self.db.query(sql_query({ "from": "sqlite_master", "where": {"eq": {"type": "table"}}, "orderby": "name" })) tables = wrap([{k: d for k, d in zip(result.header, row)} for row in result.data]) last_nested_path = ["."] for table in tables: if table.name.startswith("__"): continue base_table, nested_path = tail_field(table.name) # FIND COMMON NESTED PATH SUFFIX if nested_path == ".": last_nested_path = [] else: for i, p in enumerate(last_nested_path): if startswith_field(nested_path, p): last_nested_path = last_nested_path[i:] break else: last_nested_path = [] full_nested_path = [nested_path] + last_nested_path self._snowflakes[literal_field(base_table)] += [full_nested_path] # LOAD THE COLUMNS details = self.db.about(table.name) for cid, name, dtype, notnull, dfft_value, pk in details: if name.startswith("__"): continue cname, ctype = untyped_column(name) self.add(Column( name=cname, jx_type=coalesce(sql_type_to_json_type.get(ctype), IS_NULL), nested_path=full_nested_path, es_type=dtype, es_column=name, es_index=table.name, last_updated=Date.now() )) last_nested_path = full_nested_path
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
def device_callback(self, path=None): # HANDLE BROWESR RETURN FROM AUTH0 LOGIN error = request.args.get("error") if error: Log.error("You did it wrong") code = request.args.get("code") state = request.args.get("state") referer = request.headers.get("Referer") result = self.device.db.query( sql_query({ "from": "device", "select": "session_id", "where": { "eq": { "state": state } }, })) if not result.data: Log.error("expecting valid state") device_session_id = result.data[0][0] # GO BACK TO AUTH0 TO GET TOKENS token_request = { "client_id": self.device.auth0.client_id, "redirect_uri": self.device.auth0.redirect_uri, "code_verifier": session.code_verifier, "code": code, "grant_type": "authorization_code", } DEBUG and Log.note("Send token request to Auth0:\n {{request}}", request=token_request) auth_response = requests.request( "POST", str(URL("https://" + self.device.auth0.domain, path="oauth/token")), headers={ "Accept": "application/json", "Content-Type": "application/json", # "Referer": str(URL(self.device.auth0.redirect_uri, query={"code": code, "state": state})), }, data=value2json(token_request), ) try: auth_result = wrap(auth_response.json()) except Exception as e: Log.error("not json {{value}}", value=auth_response.content, cause=e) # VERIFY TOKENS, ADD USER TO DEVICE'S SESSION user_details = self.verify_opaque_token(auth_result.access_token) self.session_manager.update_session( device_session_id, {"user": self.permissions.get_or_create_user(user_details)}, ) # REMOVE DEVICE SETUP STATE with self.device.db.transaction() as t: t.execute(SQL_DELETE + SQL_FROM + quote_column(self.device.table) + SQL_WHERE + sql_eq(state=state)) Log.note("login complete") return Response("Login complete. You may close this page", status=200)