def device_login(self, path=None): """ REDIRECT BROWSER TO AUTH0 LOGIN """ state = request.args.get("state") self.session_manager.setup_session(session) session.code_verifier = bytes2base64URL(Random.bytes(32)) code_challenge = bytes2base64URL( sha256(session.code_verifier.encode("utf8"))) query = Data( client_id=self.device.auth0.client_id, redirect_uri=self.device.auth0.redirect_uri, state=state, nonce=bytes2base64URL(Random.bytes(32)), code_challenge=code_challenge, response_type="code", code_challenge_method="S256", response_mode="query", audience=self.device.auth0.audience, scope=self.device.auth0.scope, ) url = str( URL("https://" + self.device.auth0.domain + "/authorize", query=query)) Log.note("Forward browser to {{url}}", url=url) return redirect(url, code=302)
def verify_opaque_token(self, token): # Opaque Access Token url = "https://" + self.auth0.domain + "/userinfo" response = http.get_json(url, headers={"Authorization": "Bearer " + token}) DEBUG and Log.note("content: {{body|json}}", body=response) return response
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)
def clean(self): """ REMOVE ANY RECORDS THAT ARE NOT NEEDED BY QUEUE OR SUBSCRIBERS """ now = Date.now() # ANY BLOCKS TO FLUSH? with self.db.transaction() as t: result = t.query( SQL( f""" SELECT id, block_size_mb, block_start FROM {QUEUE} AS q JOIN {BLOCKS} AS b ON b.queue=q.id AND b.serial=q.block_start WHERE b.last_used < {quote_value(now-Duration(WRITE_INTERVAL))} """ ) ) for stale in rows(result): queue = first(q for q in self.queues if q.id == stale.id) queue._flush(**stale) # REMOVE UNREACHABLE MESSAGES conditions = [] for q in self.queues: conditions.append( SQL(f"(queue = {quote_value(q.id)} AND serial IN (") + SQL( f""" SELECT m.serial FROM {MESSAGES} AS m LEFT JOIN {UNCONFIRMED} as u ON u.serial = m.serial LEFT JOIN {SUBSCRIBER} as s ON s.queue = m.queue and s.id = u.subscriber LEFT JOIN {QUEUE} as q ON q.id = m.queue and m.serial >= q.block_start LEFT JOIN {SUBSCRIBER} as la ON la.queue = m.queue AND la.last_confirmed_serial < m.serial AND m.serial < la.next_emit_serial+la.look_ahead_serial WHERE m.queue = {q.id} AND s.id IS NULL AND -- STILL UNCONFIRMED POP q.id IS NULL AND -- NOT WRITTEN TO S3 YET la.id IS NULL -- NOT IN LOOK-AHEAD FOR SUBSCRIBER """ ) + SQL("))") ) with self.db.transaction() as t: if DEBUG: result = t.query( ConcatSQL( SQL(f"SELECT count(1) AS `count` FROM {MESSAGES} WHERE "), JoinSQL(SQL_OR, conditions), ) ) Log.note( "Delete {{num}} messages from database", num=first_row(result).count ) t.execute( ConcatSQL( SQL(f"DELETE FROM {MESSAGES} WHERE "), JoinSQL(SQL_OR, conditions) ) )
@verify_user def private_scoped(user): """A valid access token and an appropriate scope are required to access this route """ if requires_scope(config.auth0.scope): response = ( "Hello from a private endpoint! You need to be authenticated and have a scope of " + config.auth0.scope + " to see this." ) return jsonify(message=response) Log.error("You don't have access to {{scope}}", scope=config.auth0.scope, code=403) add_flask_rule(APP, "/api/public", public) add_flask_rule(APP, "/api/private", private) add_flask_rule(APP, "/api/private-scoped", private_scoped) config = startup.read_settings() constants.set(config.constants) Log.start(config.debug) session_manager = setup_flask_session(APP, config.session) perm = Permissions(Sqlite(config.permissions.store)) auth = Authenticator(APP, config.auth0, perm, session_manager) Log.note("start servers") setup_flask_ssl(APP, config.flask) APP.run(**config.flask)