def reset(): "Reset the password for a user account and send email." if utils.http_GET(): email = flask.request.args.get("email") or "" email = email.lower() return flask.render_template("user/reset.html", email=email) elif utils.http_POST(): try: user = get_user(email=flask.request.form["email"]) if user is None: raise KeyError if user["status"] != constants.ENABLED: raise KeyError except KeyError: pass # Silent when no user found. else: with UserSaver(user) as saver: saver.set_password() send_password_code(user, "password reset") # Don't advertise whether user exists or not. utils.flash_message( "An email has been sent, if a user account with the given email address exists." ) return flask.redirect(flask.url_for("home"))
def login(): "Login to a user account." if utils.http_GET(): try: flask.session["login_target_url"] = flask.request.args[ "login_target_url"] except KeyError: pass return flask.render_template("user/login.html") elif utils.http_POST(): try: do_login(flask.request.form.get("username"), flask.request.form.get("password")) except ValueError: return utils.error( "Invalid user or password, or account disabled.", url=flask.url_for(".login"), ) try: url = flask.session.pop("login_target_url") except KeyError: url = flask.url_for("home") return flask.redirect(url) # HEAD request gets here: return No_Content return "", 204
def document(cid, documentname): "Download the given document (attachment file), or delete it." call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if utils.http_GET(): if not allow_view(call): return utils.error(f"You may not view the call {call['title']}.") try: stub = call["_attachments"][documentname] except KeyError: return utils.error("No such document in call.") outfile = flask.g.db.get_attachment(call, documentname) response = flask.make_response(outfile.read()) response.headers.set("Content-Type", stub["content_type"]) response.headers.set("Content-Disposition", "attachment", filename=documentname) return response elif utils.http_DELETE(): if not allow_edit(call): return utils.error("You are not allowed to edit the call.") with CallSaver(call) as saver: saver.delete_document(documentname) return flask.redirect(flask.url_for(".documents", cid=call["identifier"]))
def grant(cid): "Display grant field definitions for delete, and add field." call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if not allow_edit(call): return utils.error("You are not allowed to edit the call.") if utils.http_GET(): repeat_fields = [ f for f in call.get("grant", []) if f["type"] == constants.REPEAT ] return flask.render_template( "call/grant.html", call=call, repeat_fields=repeat_fields, reviews_count=utils.get_count("reviews", "call", call["identifier"]), ) elif utils.http_POST(): try: with CallSaver(call) as saver: saver.add_grant_field(flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".grant", cid=call["identifier"]))
def transfer(pid): "Transfer ownership of he proposal." proposal = get_proposal(pid) if proposal is None: return utils.error("No such proposal.", flask.url_for("home")) if not allow_transfer(proposal): return utils.error("You are not allowed to transfer ownership of" " this proposal.") if utils.http_GET(): return flask.render_template("proposal/transfer.html", proposal=proposal) elif utils.http_POST(): try: with ProposalSaver(proposal) as saver: value = flask.request.form.get("user") if value: user = anubis.user.get_user(username=value, email=value) if user: saver.set_user(user) else: raise ValueError("No such user.") except ValueError as error: return utils.error(error) return flask.redirect( flask.url_for(".display", pid=proposal["identifier"]))
def register(): "Register a new user account." if utils.http_GET(): return flask.render_template("user/register.html", gdpr=utils.get_site_text("gdpr.md")) elif utils.http_POST(): try: with UserSaver() as saver: saver.set_username(flask.request.form.get("username")) saver.set_email(flask.request.form.get("email")) if utils.to_bool(flask.request.form.get("enable")): saver.set_status(constants.ENABLED) saver.set_role(constants.USER) saver.set_call_creator(False) saver.set_password() # Sets code. saver.set_givenname(flask.request.form.get("givenname")) saver.set_familyname(flask.request.form.get("familyname")) saver.set_gender(flask.request.form.get("gender")) saver.set_birthdate(flask.request.form.get("birthdate")) saver.set_degree(flask.request.form.get("degree")) saver.set_affiliation( flask.request.form.get("affiliation") or flask.request.form.get("affiliation_other")) saver.set_postaladdress( flask.request.form.get("postaladdress")) saver.set_phone(flask.request.form.get("phone")) user = saver.doc except ValueError as error: return utils.error(error, flask.url_for(".register")) # Directly enabled; send code to the user, if so instructed. if user["status"] == constants.ENABLED: if utils.to_bool(flask.request.form.get("send_email")): send_password_code(user, "registered") utils.flash_message( "User account created; an email with a link" " to set password has been sent.") else: utils.flash_message("User account created.") # Was set to 'pending'; send email to admins. else: utils.flash_message("User account created; an email will be sent" " when it has been enabled by the admin.") admins = get_users(role=constants.ADMIN, status=constants.ENABLED) recipients = [u["email"] for u in admins if u["email"]] site = flask.current_app.config["SITE_NAME"] title = f"{site} user account pending" url = flask.url_for(".display", username=user["username"], _external=True) text = f"To enable the user account, go to {url}\n\n" "/The Anubis system" utils.send_email(recipients, title, text) if flask.g.am_admin: return flask.redirect(flask.url_for("user.all")) else: return flask.redirect(flask.url_for("home"))
def edit(username): "Edit the user. Or delete the user." user = get_user(username=username) if user is None: return utils.error("No such user.", flask.url_for("home")) if not allow_edit(user): return utils.error("Access to user edit not allowed.") if utils.http_GET(): return flask.render_template("user/edit.html", user=user, allow_change_role=allow_change_role(user)) elif utils.http_POST(): try: with UserSaver(user) as saver: if flask.g.am_admin: email = flask.request.form.get("email") saver.set_email(email, require=bool(email)) if allow_change_role(user): saver.set_role(flask.request.form.get("role")) saver.set_call_creator( utils.to_bool(flask.request.form.get("call_creator"))) saver.set_givenname(flask.request.form.get("givenname")) saver.set_familyname(flask.request.form.get("familyname")) saver.set_gender(flask.request.form.get("gender")) saver.set_birthdate(flask.request.form.get("birthdate")) saver.set_degree(flask.request.form.get("degree")) saver.set_affiliation( flask.request.form.get("affiliation") or flask.request.form.get("affiliation_other")) saver.set_postaladdress( flask.request.form.get("postaladdress")) saver.set_phone(flask.request.form.get("phone")) except ValueError as error: utils.flash_error(error) return flask.redirect( flask.url_for(".display", username=user["username"])) elif utils.http_DELETE(): if not allow_delete(user): return utils.error( "Cannot delete the user account; admin or not empty.", flask.url_for(".display", username=username), ) flask.g.db.delete(user) utils.flash_message(f"Deleted user {username}.") if flask.g.am_admin: return flask.redirect(flask.url_for(".all")) else: return flask.redirect(flask.url_for("home"))
def edit(cid): "Edit the call, or delete it." call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if not allow_edit(call): return utils.error("You are not allowed to edit the call.") if utils.http_GET(): return flask.render_template( "call/edit.html", call=call, allow_identifier_edit=allow_identifier_edit(call), ) elif utils.http_POST(): try: with CallSaver(call) as saver: saver.set_identifier(flask.request.form.get("identifier")) saver.set_title(flask.request.form.get("title")) saver["description"] = flask.request.form.get("description") saver["home_description"] = ( flask.request.form.get("home_description").strip() or None ) saver["opens"] = utils.normalize_datetime( flask.request.form.get("opens") ) saver["closes"] = utils.normalize_datetime( flask.request.form.get("closes") ) saver["reviews_due"] = utils.normalize_datetime( flask.request.form.get("reviews_due") ) saver.edit_access(flask.request.form) call = saver.doc except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".display", cid=call["identifier"])) elif utils.http_DELETE(): if not allow_delete(call): return utils.error("You are not allowed to delete the call.") utils.delete(call) utils.flash_message(f"Deleted call {call['identifier']}:{call['title']}.") return flask.redirect( flask.url_for("calls.owner", username=flask.g.current_user["username"]) )
def create(): "Create a new call from scratch." if not allow_create(): return utils.error("You are not allowed to create a call.") if utils.http_GET(): return flask.render_template("call/create.html") elif utils.http_POST(): try: with CallSaver() as saver: saver.set_identifier(flask.request.form.get("identifier")) saver.set_title(flask.request.form.get("title")) call = saver.doc except ValueError as error: return utils.error(error) return flask.redirect(flask.url_for(".edit", cid=call["identifier"]))
def decision(cid): "Display decision field definitions for delete, and add field." call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if not allow_edit(call): return utils.error("You are not allowed to edit the call.") if utils.http_GET(): return flask.render_template("call/decision.html", call=call) elif utils.http_POST(): try: with CallSaver(call) as saver: saver.add_decision_field(flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".decision", cid=call["identifier"]))
def clone(cid): """Clone the call. Copies: - Title - Description - Access fields - Proposal fields - Review fields - Decision fields - Grant fields Does not copy: - Dates - Documents - Reviewers """ call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if not allow_create(): return utils.error("You are not allowed to create a call.") if utils.http_GET(): return flask.render_template("call/clone.html", call=call) elif utils.http_POST(): try: with CallSaver() as saver: saver.set_identifier(flask.request.form.get("identifier")) saver.set_title(flask.request.form.get("title")) saver.doc["description"] = call["description"] saver.doc["access"] = call["access"] saver.doc["proposal"] = copy.deepcopy(call["proposal"]) saver.doc["review"] = copy.deepcopy(call["review"]) saver.doc["decision"] = copy.deepcopy(call.get("decision", [])) saver.doc["grant"] = copy.deepcopy(call.get("grant", [])) # Do not copy dates. # Do not copy documents. # Do not copy reviewers or chairs. new = saver.doc except ValueError as error: return utils.error(error) return flask.redirect(flask.url_for(".edit", cid=new["identifier"]))
def documents(cid): "Display documents for delete, or add document (attachment file)." call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if not allow_edit(call): return utils.error("You are not allowed to edit the call.") if utils.http_GET(): return flask.render_template("call/documents.html", call=call) elif utils.http_POST(): infile = flask.request.files.get("document") if infile: description = flask.request.form.get("document_description") with CallSaver(call) as saver: saver.add_document(infile, description) else: utils.flash_error("No document selected.") return flask.redirect(flask.url_for(".display", cid=call["identifier"]))
def access(gid): "Edit the access privileges for the grant record." grant = get_grant(gid) if grant is None: return utils.error("No such grant.") if not allow_change_access(grant): return utils.error("You are not allowed to change access" " for this grant dossier.") call = anubis.call.get_call(grant["call"]) if utils.http_GET(): users = {} for user in grant.get("access_view", []): users[user] = False for user in grant.get("access_edit", []): users[user] = True return flask.render_template( "access.html", title=f"Grant {grant['identifier']}", url=flask.url_for(".access", gid=grant["identifier"]), users=users, back_url=flask.url_for(".display", gid=grant["identifier"]), ) elif utils.http_POST(): try: with GrantSaver(doc=grant) as saver: saver.set_access(form=flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".access", gid=grant["identifier"])) elif utils.http_DELETE(): try: with GrantSaver(doc=grant) as saver: saver.remove_access(form=flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".access", gid=grant["identifier"]))
def edit(gid): "Edit the grant dossier." grant = get_grant(gid) if grant is None: return utils.error("No such grant.") call = anubis.call.get_call(grant["call"]) if utils.http_GET(): if not allow_edit(grant): return utils.error( "You are not allowed to edit this grant dossier.") return flask.render_template("grant/edit.html", grant=grant, call=call) elif utils.http_POST(): if not allow_edit(grant): return utils.error( "You are not allowed to edit this grant dossier.") try: with GrantSaver(doc=grant) as saver: saver.set_fields_values(call.get("grant", []), form=flask.request.form) except ValueError as error: return utils.error(error) if saver.repeat_changed: url = flask.url_for(".edit", gid=grant["identifier"]) else: url = flask.url_for(".display", gid=grant["identifier"]) return flask.redirect(url) elif utils.http_DELETE(): if not allow_delete(grant): return utils.error( "You are not allowed to delete this grant dossier.") proposal = anubis.proposal.get_proposal(grant["proposal"]) with anubis.proposal.ProposalSaver(proposal) as saver: saver["grant"] = None utils.delete(grant) utils.flash_message("Deleted grant dossier.") return flask.redirect( flask.url_for("proposal.display", pid=proposal["identifier"]))
def access(pid): "Edit the access privileges for the proposal record." proposal = get_proposal(pid) if proposal is None: return utils.error("No such proposal.", flask.url_for("home")) if not allow_edit(proposal): return utils.error("You are not allowed to edit this proposal.") call = anubis.call.get_call(proposal["call"]) if utils.http_GET(): users = {} for user in proposal.get("access_view", []): users[user] = False for user in proposal.get("access_edit", []): users[user] = True return flask.render_template( "access.html", title=f"Proposal {proposal['identifier']}", url=flask.url_for(".access", pid=proposal["identifier"]), users=users, back_url=flask.url_for(".display", pid=proposal["identifier"]), ) elif utils.http_POST(): try: with ProposalSaver(doc=proposal) as saver: saver.set_access(form=flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect( flask.url_for(".access", pid=proposal["identifier"])) elif utils.http_DELETE(): try: with ProposalSaver(doc=proposal) as saver: saver.remove_access(form=flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect( flask.url_for(".access", pid=proposal["identifier"]))
def edit(iuid): "Edit the decision." try: decision = get_decision(iuid) except KeyError: return utils.error("No such decision.", flask.url_for("home")) proposal = anubis.proposal.get_proposal(decision["proposal"]) call = anubis.call.get_call(decision["call"]) if utils.http_GET(): if not allow_edit(decision): return utils.error("You are not allowed to edit this decision.") return flask.render_template("decision/edit.html", decision=decision, proposal=proposal, call=call) elif utils.http_POST(): if not allow_edit(decision): return utils.error("You are not allowed to edit this decision.") try: # NOTE: Repeat field has not been implemented for decision. with DecisionSaver(doc=decision) as saver: saver.set_verdict(form=flask.request.form) saver.set_fields_values(call["decision"], form=flask.request.form) except ValueError as error: return utils.error(error) return flask.redirect(flask.url_for(".display", iuid=decision["_id"])) elif utils.http_DELETE(): if not allow_delete(decision): return utils.error("You are not allowed to delete this decision.") with anubis.proposal.ProposalSaver(proposal) as saver: saver["decision"] = None utils.delete(decision) utils.flash_message("Deleted decision.") return flask.redirect( flask.url_for("proposal.display", pid=proposal["identifier"]))
def password(): "Set the password for a user account, and login user." if utils.http_GET(): return flask.render_template( "user/password.html", username=flask.request.args.get("username"), code=flask.request.args.get("code"), ) elif utils.http_POST(): try: username = flask.request.form.get("username") or "" if not username: raise ValueError("No such user or wrong code.") user = get_user(username=username) if user is None: raise ValueError("No such user or wrong code.") if flask.g.am_admin: code = "" else: code = flask.request.form.get("code") or "" if user["password"] != f"code:{code}": raise ValueError("No such user or wrong code.") password = flask.request.form.get("password") or "" if len(password) < flask.current_app.config["MIN_PASSWORD_LENGTH"]: raise ValueError("Too short password.") except ValueError as error: return utils.error( error, flask.url_for(".password", username=username, code=code)) with UserSaver(user) as saver: saver.set_password(password) utils.flash_message("Password set.") if flask.g.am_admin: return flask.redirect(flask.url_for(".all")) else: do_login(username, password) return flask.redirect(flask.url_for("home"))
def edit(iuid): "Edit the review for the proposal." try: review = get_review(iuid) except KeyError: return utils.error("No such review.", flask.url_for("home")) proposal = anubis.proposal.get_proposal(review["proposal"]) call = anubis.call.get_call(review["call"]) if utils.http_GET(): if not allow_edit(review): return utils.error("You are not allowed to edit this review.") return flask.render_template("review/edit.html", review=review, proposal=proposal, call=call) elif utils.http_POST(): if not allow_edit(review): return utils.error("You are not allowed to edit this review.") try: # NOTE: Repeat field has not been implemented for review. with ReviewSaver(doc=review) as saver: saver.set_fields_values(call["review"], form=flask.request.form) except ValueError as error: return utils.error(error) return flask.redirect(flask.url_for(".display", iuid=review["_id"])) elif utils.http_DELETE(): if not allow_delete(review): return utils.error("You are not allowed to delete this review.") utils.delete(review) utils.flash_message("Deleted review.") return flask.redirect( flask.url_for("proposal.display", pid=review["proposal"]))
def access(cid): "Edit the access privileges for the call." call = get_call(cid) if call is None: return utils.error("No such call.") if not allow_change_access(call): return utils.error("You are not allowed to change access for this call.") if utils.http_GET(): users = {} for user in call.get("access_view", []): users[user] = False for user in call.get("access_edit", []): users[user] = True return flask.render_template( "access.html", title=f"Call {call['identifier']}", url=flask.url_for(".access", cid=call["identifier"]), users=users, back_url=flask.url_for(".display", cid=call["identifier"]), ) elif utils.http_POST(): try: with CallSaver(doc=call) as saver: saver.set_access(form=flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".access", cid=call["identifier"])) elif utils.http_DELETE(): try: with CallSaver(doc=call) as saver: saver.remove_access(form=flask.request.form) except ValueError as error: utils.flash_error(error) return flask.redirect(flask.url_for(".access", cid=call["identifier"]))
def edit(pid): "Edit the proposal." proposal = get_proposal(pid) if proposal is None: return utils.error("No such proposal.", flask.url_for("home")) call = anubis.call.get_call(proposal["call"]) if utils.http_GET(): if not allow_edit(proposal): return utils.error("You are not allowed to edit this proposal.") return flask.render_template("proposal/edit.html", proposal=proposal, call=call) elif utils.http_POST(): if not allow_edit(proposal): return utils.error("You are not allowed to edit this proposal.") try: with ProposalSaver(proposal) as saver: saver["title"] = flask.request.form.get("_title") or None saver.set_fields_values(call["proposal"], form=flask.request.form) except ValueError as error: return utils.error(error) # If a repeat field was changed, then redisplay edit page. if saver.repeat_changed: return flask.redirect( flask.url_for(".edit", pid=proposal["identifier"])) if flask.request.form.get("_save") == "submit": proposal = get_proposal(pid, refresh=True) # Get up-to-date info. try: with ProposalSaver(proposal) as saver: saver.set_submitted() # Tests whether allowed or not. except ValueError as error: utils.flash_error(error) else: utils.flash_message("Proposal saved and submitted.") send_submission_email(proposal) elif allow_submit(proposal) and not proposal.get("submitted"): utils.flash_warning("Proposal was saved but not submitted." " You must explicitly submit it!") return flask.redirect( flask.url_for(".display", pid=proposal["identifier"])) elif utils.http_DELETE(): if not allow_delete(proposal): return utils.error("You are not allowed to delete this proposal.") decision = anubis.decision.get_decision(proposal.get("decision")) if decision: utils.delete(decision) reviews = utils.get_docs_view("reviews", "proposal", proposal["identifier"]) for review in reviews: utils.delete(review) utils.delete(proposal) utils.flash_message(f"Deleted proposal {pid}.") if flask.g.am_admin or flask.g.am_staff: url = flask.url_for("proposals.call", cid=call["identifier"]) else: url = flask.url_for("proposals.user", username=proposal["user"]) return flask.redirect(url)
def reviewers(cid): "Edit the list of reviewers." call = get_call(cid) if not call: return utils.error("No such call.", flask.url_for("home")) if utils.http_GET(): if not allow_view_details(call): return utils.error("You are not allowed to edit the reviewers of the call.") reviewers = [anubis.user.get_user(r) for r in call["reviewers"]] reviewer_emails = [r["email"] for r in reviewers if r["email"]] email_lists = {"Emails for reviewers": ", ".join(reviewer_emails)} return flask.render_template( "call/reviewers.html", call=call, reviewers=reviewers, email_lists=email_lists, allow_edit=allow_edit(call), allow_view_reviews=allow_view_reviews(call), ) elif utils.http_POST(): if not allow_edit(call): return utils.error("You are not allowed to edit the call.") reviewer = flask.request.form.get("reviewer") if not reviewer: return flask.redirect(flask.url_for(".display", cid=cid)) user = anubis.user.get_user(username=reviewer) if user is None: user = anubis.user.get_user(email=reviewer) if user is None: return utils.error("No such user.") if anubis.proposal.get_call_user_proposal(cid, user["username"]): utils.flash_warning( "User has a proposal in the call. Allowing" " her to be a reviewer is questionable." ) with CallSaver(call) as saver: try: saver["reviewers"].remove(user["username"]) except ValueError: pass try: saver["chairs"].remove(user["username"]) except ValueError: pass saver["reviewers"].append(user["username"]) if utils.to_bool(flask.request.form.get("chair")): saver["chairs"].append(user["username"]) return flask.redirect(flask.url_for(".reviewers", cid=call["identifier"])) elif utils.http_DELETE(): if not allow_edit(call): return utils.error("You are not allowed to edit the call.") reviewer = flask.request.form.get("reviewer") if utils.get_docs_view( "reviews", "call_reviewer", [call["identifier"], reviewer] ): return utils.error( "Cannot remove reviewer which has reviews" " in the call." ) if reviewer: with CallSaver(call) as saver: try: saver["reviewers"].remove(reviewer) except ValueError: pass try: saver["chairs"].remove(reviewer) except ValueError: pass return flask.redirect(flask.url_for(".reviewers", cid=call["identifier"]))