def toggle_favourite(key): """ 'Like' a dataset Marks the dataset as being liked by the currently active user, which can be used for organisation in the front-end. :param str key: Key of the dataset to mark as favourite. :return: A JSON object with the status of the request :return-schema: {type=object,properties={success={type=boolean},favourite_status={type=boolean}}} :return-error 404: If the dataset key was not found """ try: dataset = DataSet(key=key, db=db) except TypeError: return error(404, error="Dataset does not exist.") current_status = db.fetchone("SELECT * FROM users_favourites WHERE name = %s AND key = %s", (current_user.get_id(), dataset.key)) if not current_status: db.insert("users_favourites", data={"name": current_user.get_id(), "key": dataset.key}) return jsonify({"success": True, "favourite_status": True}) else: db.delete("users_favourites", where={"name": current_user.get_id(), "key": dataset.key}) return jsonify({"success": True, "favourite_status": False})
def load_user_from_request(request): """ Load user object via access token Access token may be supplied via a GET parameter or the Authorization HTTP header. :param request: Flask request :return: User object, or None if no valid access token was given """ token = request.args.get("access-token") if not token: token = request.headers.get("Authorization") if not token: return None user = db.fetchone( "SELECT name AS user FROM access_tokens WHERE token = %s AND (expires = 0 OR expires > %s)", (token, int(time.time()))) if not user: return None else: db.execute( "UPDATE access_tokens SET calls = calls + 1 WHERE name = %s", (user["user"], )) user = User.get_by_name(user["user"]) user.authenticate() return user
def api_thread(datasource, board, thread_id): """ Emulate 4chan thread.json API endpoint :param str datasource: Data source ID :param str board: Board name :param int thread_id: Thread ID :request-param str format: Data format. Can be `json` (default) or `html`. :return: Thread data, as a list of `posts`. :return-schema: {type=object,properties={posts={type=object,additionalProperties={}}}} :return-error 404: If the thread ID does not exist for the given data source. """ if datasource not in config.DATASOURCES: return error(404, error="Invalid data source") thread = db.fetchone( "SELECT * FROM threads_" + datasource + " WHERE board = %s AND id = %s", (board, thread_id)) if thread == None: return "Thread is not anymore available on the server." response = get_thread(datasource, board, thread, db) def strip_html(post): post["com"] = strip_tags(post.get("com", "")) return post response["posts"] = [strip_html(post) for post in response["posts"]] if not response: return error(404, error="No posts available for this datasource") elif request.args.get("format", "json") == "html": def format(post): post["com"] = format_post(post.get("com", "")).replace("\n", "<br>") return post response["posts"] = [format(post) for post in response["posts"]] metadata = { "subject": "".join([post.get("sub", "") for post in response["posts"]]), "id": response["posts"][0]["no"] } return render_template("thread.html", datasource=datasource, board=board, posts=response["posts"], thread=thread, metadata=metadata) else: return jsonify(response)
def show_result(key): """ Show result page The page contains dataset details and a download link, but also shows a list of finished and available processors. :param key: Result key :return: Rendered template """ try: dataset = DataSet(key=key, db=db) except TypeError: abort(404) # child datasets are not available via a separate page - redirect to parent if dataset.key_parent: genealogy = dataset.get_genealogy() nav = ",".join([family.key for family in genealogy]) url = "/results/%s/#nav=%s" % (genealogy[0].key, nav) return redirect(url) # load list of processors compatible with this dataset is_processor_running = False # show preview if dataset.is_finished() and dataset.num_rows > 0: preview = get_preview(dataset) else: preview = None is_favourite = (db.fetchone( "SELECT COUNT(*) AS num FROM users_favourites WHERE name = %s AND key = %s", (current_user.get_id(), dataset.key))["num"] > 0) # if the datasource is configured for it, this dataset may be deleted at some point datasource = dataset.parameters.get("datasource", "") if datasource in backend.all_modules.datasources and backend.all_modules.datasources[ datasource].get("expire-datasets", None): timestamp_expires = dataset.timestamp + int( backend.all_modules.datasources[datasource].get("expire-datasets")) else: timestamp_expires = None # we can either show this view as a separate page or as a bunch of html # to be retrieved via XHR standalone = "processors" not in request.url template = "result.html" if standalone else "result-details.html" return render_template(template, preview=preview, dataset=dataset, parent_key=dataset.key, processors=backend.all_modules.processors, is_processor_running=is_processor_running, messages=get_flashed_messages(), is_favourite=is_favourite, timestamp_expires=timestamp_expires)
def show_results(page): """ Show results overview For each result, available analyses are also displayed. :return: Rendered template """ page_size = 20 offset = (page - 1) * page_size where = ["key_parent = ''"] replacements = [] query_filter = request.args.get("filter", "") depth = request.args.get("depth", "own") if depth not in ("own", "favourites", "all"): depth = "own" if depth == "own": where.append("parameters::json->>'user' = %s") replacements.append(current_user.get_id()) if depth == "favourites": where.append("key IN ( SELECT key FROM users_favourites WHERE name = %s )") replacements.append(current_user.get_id()) if query_filter: where.append("query LIKE %s") replacements.append("%" + query_filter + "%") where = " AND ".join(where) num_datasets = db.fetchone("SELECT COUNT(*) AS num FROM datasets WHERE " + where, tuple(replacements))["num"] replacements.append(page_size) replacements.append(offset) datasets = db.fetchall("SELECT key FROM datasets WHERE " + where + " ORDER BY timestamp DESC LIMIT %s OFFSET %s", tuple(replacements)) if not datasets and page != 1: abort(404) pagination = Pagination(page, page_size, num_datasets) filtered = [] processors = backend.all_modules.processors for dataset in datasets: filtered.append(DataSet(key=dataset["key"], db=db)) favourites = [row["key"] for row in db.fetchall("SELECT key FROM users_favourites WHERE name = %s", (current_user.get_id(),))] return render_template("results.html", filter={"filter": query_filter}, depth=depth, datasets=filtered, pagination=pagination, favourites=favourites)
def get_by_name(name): """ Get user object for given user name :return: User object, or `None` for invalid user name """ user = db.fetchone("SELECT * FROM users WHERE name = %s", (name,)) if not user: return None else: return User(user)
def get_by_token(token): """ Get user object for given token, if token is valid :return: User object, or `None` for invalid token """ user = db.fetchone( "SELECT * FROM users WHERE register_token = %s AND (timestamp_token = 0 OR timestamp_token > %s)", (token, int(time.time()) - (3 * 86400))) if not user: return None else: return User(user)
def request_token(): """ Request an access token Requires that the user is currently logged in to 4CAT. :return: An object with one item `token` :return-schema={type=object,properties={token={type=string}}} :return-error 403: If the user is logged in with an anonymous account. """ if current_user.get_id() == "autologin": # access tokens are only for 'real' users so we can keep track of who # (ab)uses them return error(403, error="Anonymous users may not request access tokens.") token = db.fetchone( "SELECT * FROM access_tokens WHERE name = %s AND (expires = 0 OR expires > %s)", (current_user.get_id(), int(time.time()))) if token: token = token["token"] else: token = current_user.get_id() + str(time.time()) token = hashlib.sha256(token.encode("utf8")).hexdigest() token = { "name": current_user.get_id(), "token": token, "expires": int(time.time()) + (365 * 86400) } # delete any expired tokens db.delete("access_tokens", where={"name": current_user.get_id()}) # save new token db.insert("access_tokens", token) if request.args.get("forward"): # show HTML page return redirect(url_for("show_access_tokens")) else: # show JSON response (by default) return jsonify(token)
def get_by_login(name, password): """ Get user object, if login is correct If the login data supplied to this method is correct, a new user object that is marked as authenticated is returned. :param name: User name :param password: User password :return: User object, or `None` if login was invalid """ user = db.fetchone("SELECT * FROM users WHERE name = %s", (name,)) if not user or not user.get("password", None): # registration not finished yet return None elif not user or not bcrypt.checkpw(password.encode("ascii"), user["password"].encode("ascii")): # non-existing user or wrong password return None else: # valid login! return User(user, authenticated=True)
def check_dataset(): """ Check dataset status Requires authentication by logging in or providing a valid access token. :request-param str key: ID of the dataset for which to return the status :return: Dataset status, containing the `status`, `query`, number of `rows`, the dataset `key`, whether the dataset is `done`, the `path` of the result file and whether the dataset is `empty`. :return-schema: { type=object, properties={ status={type=string}, query={type=string}, rows={type=integer}, key={type=string}, done={type=boolean}, path={type=string}, empty={type=boolean}, is_favourite={type=boolean} } } :return-error 404: If the dataset does not exist. """ dataset_key = request.args.get("key") try: dataset = DataSet(key=dataset_key, db=db) except TypeError: return error(404, error="Dataset does not exist.") results = dataset.check_dataset_finished() if results == 'empty': dataset_data = dataset.data dataset_data["parameters"] = json.loads(dataset_data["parameters"]) path = False elif results: # Return absolute folder when using localhost for debugging path = results.name dataset_data = dataset.data dataset_data["parameters"] = json.loads(dataset_data["parameters"]) else: path = "" status = { "status": dataset.get_status(), "status_html": render_template("result-status.html", dataset=dataset), "label": dataset.get_label(), "query": dataset.data["query"], "rows": dataset.data["num_rows"], "key": dataset_key, "done": True if dataset.is_finished() else False, "path": path, "empty": (dataset.data["num_rows"] == 0), "is_favourite": (db.fetchone("SELECT COUNT(*) AS num FROM users_favourites WHERE name = %s AND key = %s", (current_user.get_id(), dataset.key))["num"] > 0) } return jsonify(status)