async def reset(req): """ Handles `POST` requests for resetting the password for a session user. :param req: the request to handle :return: a response """ db = req.app["db"] session_id = req["client"].session_id password = req["data"]["password"] reset_code = req["data"]["reset_code"] session = await db.sessions.find_one(session_id) error = await virtool.users.checks.check_password_length(req) if not session.get("reset_code") or not session.get( "reset_user_id") or reset_code != session.get("reset_code"): error = "Invalid reset code" user_id = session["reset_user_id"] if error: return json_response( { "error": error, "reset_code": await virtool.users.sessions.create_reset_code( db, session_id, user_id=user_id) }, status=400) # Update the user password and disable the `force_reset`. await virtool.users.db.edit(db, user_id, force_reset=False, password=password) new_session, token = await virtool.users.sessions.replace_session( db, session_id, virtool.http.auth.get_ip(req), user_id, remember=session.get("reset_remember", False)) req["client"].authorize(new_session, is_api=False) resp = json_response({"login": False, "reset": False}, status=200) virtool.http.utils.set_session_id_cookie(resp, new_session["_id"]) virtool.http.utils.set_session_token_cookie(resp, token) # Authenticate and return a redirect response to the `return_to` path. This is identical to the process used for # successful login requests. return resp
async def find(req): """ Return a list of indexes. """ db = req.app["db"] ready = req.query.get("ready", False) if not ready: data = await virtool.indexes.db.find(db, req.query) return json_response(data) pipeline = [ { "$match": { "ready": True } }, { "$sort": { "version": -1 } }, { "$group": { "_id": "$reference.id", "index": { "$first": "$_id" }, "version": { "$first": "$version" } } } ] ready_indexes = list() async for agg in db.indexes.aggregate(pipeline): reference = await db.references.find_one(agg["_id"], ["data_type", "name"]) ready_indexes.append({ "id": agg["index"], "version": agg["version"], "reference": { "id": agg["_id"], "name": reference["name"], "data_type": reference["data_type"] } }) return json_response(ready_indexes)
async def get(req): """ Get a complete analysis document. """ db = req.app["db"] analysis_id = req.match_info["analysis_id"] document = await db.analyses.find_one(analysis_id) if document is None: return not_found() sample = await db.samples.find_one({"_id": document["sample"]["id"]}, {"quality": False}) if not sample: return bad_request("Parent sample does not exist") read, _ = virtool.samples.utils.get_sample_rights(sample, req["client"]) if not read: return insufficient_rights() await virtool.subtractions.db.attach_subtraction(db, document) if document["ready"]: document = await virtool.analyses.format.format_analysis( req.app, document) return json_response(virtool.utils.base_processor(document))
async def blast(req): """ BLAST a contig sequence that is part of a NuVs result record. The resulting BLAST data will be attached to that sequence. """ db = req.app["db"] settings = req.app["settings"] analysis_id = req.match_info["analysis_id"] sequence_index = int(req.match_info["sequence_index"]) document = await db.analyses.find_one( {"_id": analysis_id}, ["ready", "workflow", "results", "sample"]) if not document: return not_found("Analysis not found") if document["workflow"] != "nuvs": return conflict("Not a NuVs analysis") if not document["ready"]: return conflict("Analysis is still running") sequence = virtool.analyses.utils.find_nuvs_sequence_by_index( document, sequence_index) if sequence is None: return not_found("Sequence not found") sample = await db.samples.find_one({"_id": document["sample"]["id"]}, virtool.samples.db.PROJECTION) if not sample: return bad_request("Parent sample does not exist") _, write = virtool.samples.utils.get_sample_rights(sample, req["client"]) if not write: return insufficient_rights() # Start a BLAST at NCBI with the specified sequence. Return a RID that identifies the BLAST run. rid, _ = await virtool.bio.initialize_ncbi_blast(req.app["settings"], sequence) blast_data, document = await virtool.analyses.db.update_nuvs_blast( db, settings, analysis_id, sequence_index, rid) # Wait on BLAST request as a Task until the it completes on NCBI. At that point the sequence in the DB will be # updated with the BLAST result. await aiojobs.aiohttp.spawn( req, virtool.bio.wait_for_blast_result(req.app, analysis_id, sequence_index, rid)) headers = { "Location": f"/api/analyses/{analysis_id}/{sequence_index}/blast" } return json_response(blast_data, headers=headers, status=201)
async def install(req): db = req.app["db"] releases = await virtool.db.utils.get_one_field(db.status, "releases", "software") try: latest_release = releases[0] except IndexError: return not_found("Could not find latest uninstalled release") process = await virtool.processes.db.register( db, "update_software", context={"file_size": latest_release["size"]}) await db.status.update_one( {"_id": "software"}, {"$set": { "process": process, "updating": True }}) update = virtool.github.create_update_subdocument( latest_release, False, req["client"].user_id, virtool.utils.timestamp()) await aiojobs.aiohttp.spawn( req, virtool.software.db.install(req.app, latest_release, process["id"])) return json_response(update)
async def list_releases(req): try: releases = await virtool.software.db.fetch_and_update_releases(req.app) except aiohttp.ClientConnectorError: return bad_gateway("Could not connection to www.virtool.ca") return json_response(releases)
async def set_as_default(req): """ Set an isolate as default. """ db = req.app["db"] otu_id = req.match_info["otu_id"] isolate_id = req.match_info["isolate_id"] document = await db.otus.find_one({"_id": otu_id, "isolates.id": isolate_id}, ["reference"]) if not document: return not_found() if not await virtool.references.db.check_right(req, document["reference"]["id"], "modify_otu"): return insufficient_rights() isolate = await asyncio.shield(virtool.otus.isolates.set_default( req.app, otu_id, isolate_id, req["client"].user_id )) return json_response(isolate)
async def create(req): """ Add a new user to the user database. """ db = req.app["db"] data = await req.json() if data["user_id"] == "virtool": return bad_request("Reserved user name: virtool") error = await virtool.users.checks.check_password_length(req) if error: return bad_request(error) user_id = data["user_id"] try: document = await virtool.users.db.create(db, user_id, data["password"], data["force_reset"]) except virtool.errors.DatabaseError: return bad_request("User already exists") headers = {"Location": f"/api/users/{user_id}"} return json_response(virtool.utils.base_processor( {key: document[key] for key in virtool.users.db.PROJECTION}), headers=headers, status=201)
async def find_history(req): """ Find history changes for a specific index. """ db = req.app["db"] index_id = req.match_info["index_id"] if not await db.indexes.count_documents({"_id": index_id}): return not_found() term = req.query.get("term") db_query = { "index.id": index_id } if term: db_query.update(virtool.api.utils.compose_regex_query(term, ["otu.name", "user.id"])) data = await virtool.api.utils.paginate( db.history, db_query, req.query, sort=[("otu.name", 1), ("otu.version", -1)], projection=virtool.history.db.LIST_PROJECTION, reverse=True ) return json_response(data)
async def install(req): """ Install the latest official HMM database from GitHub. """ db = req.app["db"] user_id = req["client"].user_id if await db.status.count_documents({"_id": "hmm", "updates.ready": False}): return conflict("Install already in progress") process = await virtool.processes.db.register(db, "install_hmms") document = await db.status.find_one_and_update( {"_id": "hmm"}, {"$set": { "process": { "id": process["id"] } }}) release = document.get("release") if release is None: return bad_request("Target release does not exist") update = virtool.github.create_update_subdocument(release, False, user_id) await db.status.update_one({"_id": "hmm"}, {"$push": {"updates": update}}) await aiojobs.aiohttp.spawn( req, virtool.hmm.db.install(req.app, process["id"], release, user_id)) return json_response(update)
async def find(req): """ Find files based on an optional text query that is matched against file names. Only ready, unreserved files are returned. """ db = req.app["db"] base_query = {"ready": True, "reserved": False} file_type = req.query.get("type") db_query = dict() if file_type: base_query["type"] = file_type data = await virtool.api.utils.paginate( db.files, db_query, req.query, sort=[("uploaded_at", pymongo.DESCENDING)], projection=virtool.files.db.PROJECTION, base_query=base_query) return json_response(data)
async def find(req): db = req.app["db"] term = req.query.get("find") db_query = dict() if term: db_query = virtool.api.utils.compose_regex_query( term, ["name", "data_type"]) base_query = virtool.references.db.compose_base_find_query( req["client"].user_id, req["client"].administrator, req["client"].groups) data = await virtool.api.utils.paginate( db.references, db_query, req.query, sort="name", base_query=base_query, projection=virtool.references.db.PROJECTION) data["documents"] = [ await virtool.references.db.processor(db, d) for d in data["documents"] ] data[ "official_installed"] = await virtool.references.db.get_official_installed( db) return json_response(data)
async def set_rights(req): """ Change rights settings for the specified sample document. """ db = req.app["db"] data = req["data"] sample_id = req.match_info["sample_id"] if not await db.samples.count_documents({"_id": sample_id}): return not_found() user_id = req["client"].user_id # Only update the document if the connected user owns the samples or is an administrator. if not req["client"].administrator and user_id != await virtool.samples.db.get_sample_owner(db, sample_id): return insufficient_rights("Must be administrator or sample owner") group = data.get("group") if group: existing_group_ids = await db.groups.distinct("_id") + ["none"] if group not in existing_group_ids: return bad_request("Group does not exist") # Update the sample document with the new rights. document = await db.samples.find_one_and_update({"_id": sample_id}, { "$set": data }, projection=virtool.samples.db.RIGHTS_PROJECTION) return json_response(document)
async def edit(req): """ Update specific fields in the sample document. """ db = req.app["db"] data = req["data"] sample_id = req.match_info["sample_id"] if not await virtool.samples.db.check_rights(db, sample_id, req["client"]): return insufficient_rights() message = await virtool.samples.db.check_name(db, req.app["settings"], data["name"], sample_id=sample_id) if message: return bad_request(message) document = await db.samples.find_one_and_update({"_id": sample_id}, { "$set": data }, projection=virtool.samples.db.LIST_PROJECTION) processed = virtool.utils.base_processor(document) return json_response(processed)
async def remove(req): """ Remove a reference and its otus, history, and indexes. """ db = req.app["db"] ref_id = req.match_info["ref_id"] if not await virtool.db.utils.id_exists(db.references, ref_id): return not_found() if not await virtool.references.db.check_right(req, ref_id, "remove"): return insufficient_rights() user_id = req["client"].user_id context = {"ref_id": ref_id, "user_id": user_id} process = await virtool.processes.db.register(db, "delete_reference", context=context) await db.references.delete_one({"_id": ref_id}) p = virtool.references.db.RemoveReferenceProcess(req.app, process["id"]) await aiojobs.aiohttp.spawn(req, p.run()) headers = {"Content-Location": f"/api/processes/{process['id']}"} return json_response(process, 202, headers)
async def edit_user(req): db = req.app["db"] data = req["data"] ref_id = req.match_info["ref_id"] user_id = req.match_info["user_id"] document = await db.references.find_one( { "_id": ref_id, "users.id": user_id }, ["groups", "users"]) if document is None: return not_found() if not await virtool.references.db.check_right(req, ref_id, "modify"): return insufficient_rights() subdocument = await virtool.references.db.edit_group_or_user( db, ref_id, user_id, "users", data) if subdocument is None: return not_found() subdocument = await virtool.users.db.attach_identicons(db, subdocument) return json_response(subdocument)
async def upload(req): db = req.app["db"] file_type = req.match_info["file_type"] if file_type not in FILE_TYPES: return not_found() errors = naive_validator(req) if errors: return invalid_query(errors) filename = req.query["name"] document = await virtool.files.db.create( db, filename, file_type, user_id=req["client"].user_id ) file_id = document["id"] await naive_writer(req, file_id) await virtool.uploads.db.finish_upload(req.app, file_id) headers = { "Location": f"/api/files/{file_id}" } return json_response(document, status=201, headers=headers)
async def get_release(req): """ Get the latest update from GitHub and return it. Also updates the reference document. This is the only way of doing so without waiting for an automatic refresh every 10 minutes. """ db = req.app["db"] ref_id = req.match_info["ref_id"] if not await virtool.db.utils.id_exists(db.references, ref_id): return not_found() if not await db.references.count_documents({ "_id": ref_id, "remotes_from": { "$exists": True } }): return bad_request("Not a remote reference") try: release = await virtool.references.db.fetch_and_update_release( req.app, ref_id) except aiohttp.ClientConnectorError: return bad_gateway("Could not reach GitHub") if release is None: return bad_gateway("Release repository does not exist on GitHub") return json_response(release)
async def edit(req): """ Updates the nickname for an existing subtraction. """ db = req.app["db"] data = req["data"] subtraction_id = req.match_info["subtraction_id"] update = dict() try: update["name"] = data["name"] except KeyError: pass try: update["nickname"] = data["nickname"] except KeyError: pass document = await db.subtraction.find_one_and_update({"_id": subtraction_id}, { "$set": update }) if document is None: return not_found() document["linked_samples"] = await virtool.subtractions.db.get_linked_samples(db, subtraction_id) return json_response(virtool.utils.base_processor(document))
async def add_user(req): db = req.app["db"] data = req["data"] ref_id = req.match_info["ref_id"] document = await db.references.find_one(ref_id, ["groups", "users"]) if document is None: return not_found() if not await virtool.references.db.check_right(req, ref_id, "modify"): return insufficient_rights() try: subdocument = await virtool.references.db.add_group_or_user( db, ref_id, "users", data) except virtool.errors.DatabaseError as err: if "already exists" in str(err): return bad_request("User already exists") if "does not exist" in str(err): return bad_request("User does not exist") raise headers = {"Location": f"/api/refs/{ref_id}/users/{subdocument['id']}"} subdocument = await virtool.users.db.attach_identicons(db, subdocument) return json_response(subdocument, headers=headers, status=201)
async def update_permissions(req): """ Updates the permissions of a given group. """ db = req.app["db"] data = req["data"] group_id = req.match_info["group_id"] old_document = await db.groups.find_one({"_id": group_id}, ["permissions"]) if not old_document: return not_found() old_document["permissions"].update(data["permissions"]) # Get the current permissions dict for the passed group id. document = await db.groups.find_one_and_update({"_id": group_id}, { "$set": { "permissions": old_document["permissions"] } }) await virtool.groups.db.update_member_users(db, group_id) return json_response(virtool.utils.base_processor(document))
async def find(req): """ Find HMM annotation documents. """ db = req.app["db"] term = req.query.get("find") db_query = dict() if term: db_query.update(virtool.api.utils.compose_regex_query(term, ["names"])) data = await virtool.api.utils.paginate( db.hmm, db_query, req.query, sort="cluster", projection=virtool.hmm.db.PROJECTION, base_query={"hidden": False}) data["status"] = await virtool.hmm.db.get_status(db) return json_response(data)
async def find(req): """ Get a list of all existing group documents. """ cursor = req.app["db"].groups.find() return json_response([virtool.utils.base_processor(d) async for d in cursor])
async def get(req): """ Get the complete document for a given index. """ db = req.app["db"] index_id = req.match_info["index_id"] document = await db.indexes.find_one(index_id) if not document: return not_found() contributors, otus = await asyncio.gather( virtool.indexes.db.get_contributors(db, index_id), virtool.indexes.db.get_otus(db, index_id) ) document.update({ "change_count": sum(v["change_count"] for v in otus), "contributors": contributors, "otus": otus, }) document = await virtool.indexes.db.processor(db, document) return json_response(document)
async def unavailable(req): return json_response( { "id": "requires_setup", "message": "Server is not configured" }, status=503, headers={"Location": "/setup"})
async def get(req): """ Get complete user document. """ document = await virtool.account.db.get_document(req.app["db"], req["client"].user_id) return json_response(virtool.utils.base_processor(document))
async def wrapped(req): if not public and not req["client"].user_id: return json_response( { "id": "requires_authorization", "message": "Requires authorization" }, status=401) if not req["client"].administrator: if admin: return json_response( { "id": "not_permitted", "message": "Requires administrative privilege" }, status=403) if permission and not req["client"].permissions[permission]: return json_response( { "id": "not_permitted", "message": "Not permitted" }, status=403) content_type = req.headers.get("Content-type", "") if "multipart/form-data" not in content_type: try: data = await req.json() except (json.decoder.JSONDecodeError, UnicodeDecodeError): data = dict() if schema: v = Validator(schema, purge_unknown=True) if not v.validate(data): return invalid_input(v.errors) data = v.document req["data"] = data return await handler(req)
async def find(req): db = req.app["db"] documents = [ virtool.utils.base_processor(d) async for d in db.processes.find() ] return json_response(documents)
async def get_api_keys(req): db = req.app["db"] user_id = req["client"].user_id cursor = db.keys.find({"user.id": user_id}, API_KEY_PROJECTION) return json_response([d async for d in cursor], status=200)
async def get_resources(req): """ Get a object describing compute resource usage on the server. """ resources = virtool.resources.get() req.app["resources"].update(resources) return json_response(resources)