def password_login():
    if session.ok:
        session.destroy()

    email = request.form["email"].lower()
    password = request.form["password"]

    try:
        user = users.password_login("email", email, password)
    except users.ShouldUseRaven:
        return render_template("login/password-login.html",
                               should_use_raven=True, email=email)
    except users.EmailNotFound:
        return render_template("login/password-login.html",
                               bad_email=True, email=email)
    except users.BadPassword:
        return render_template("login/password-login.html",
                               bad_password=True, email=email)
    except users.UserDisabled:
        return user_disabled_response()
    else:
        logger.info("logging in user %s (via password; email %s)",
                    user["user_id"], email)

        session.create(user)
        return redirect(login_next())
def email_confirm(user_id, secret):
    if session.ok and user_id != session["user"]["user_id"]:
        session.destroy()

    try:
        users.email_confirm_check(user_id, secret,
                                  session["user"] if session.ok else None)
    except users.EmailAlreadyConfirmed:
        return render_template("login/email-confirm.html",
                               already_confirmed=True, next=login_next())
    except users.BadSecret:
        return render_template("login/email-confirm.html", bad_secret=True)

    if not session.ok and request.method == "POST":
        password = request.form["password"]
        try:
            user = users.password_login("user_id", user_id, password)
        except users.BadPassword:
            return render_template("login/email-confirm.html",
                                   unauthenticated=True, bad_password=True,
                                   user_id=user_id, secret=secret)
        else:
            logger.info("logging in user %s (during email confirmation)",
                        user["user_id"])
            session.create(user)

    if not session.ok:
        return render_template("login/email-confirm.html",
                               unauthenticated=True,
                               user_id=user_id, secret=secret)

    users.email_confirm(user_id)
    return render_template("login/email-confirm.html",
                           confirmed=True, next=login_next())
def email_reset(user_id, secret):
    if session.ok:
        session.destroy()

    # technically we could race between checking and resetting, allowing
    # a user to reset their password twice, but that achieves nothing

    try:
        user = users.reset_password_check(user_id, secret)
    except users.BadSecret:
        logger.warning("bad email_reset link (user %s)", user_id)
        return render_template("login/email-reset.html", invalid=True)

    if request.method == "GET":
        return render_template("login/email-reset.html", ready=True,
                               user_id=user_id, secret=secret,
                               email=user["email"])

    else:
        password = request.form["password"]
        password_again = request.form["password_again"]
        errors = {}

        if len(password) < 8:
            errors["password_short"] = True
        elif password != password_again:
            errors["password_again_bad"] = True

        if errors:
            return render_template("login/email-reset.html",
                                   user_id=user_id, secret=secret,
                                   email=user["email"],
                                   change_failed=True, **errors)

        else:
            users.reset_password(user_id, password)
            if not user["email_confirmed"]:
                # user arrived here via a link in an email, and is logged in
                users.email_confirm(user_id)
            session.create(user)

            return render_template("login/email-reset.html",
                                   success=True, next=login_next())
def password_signup():
    if session.ok:
        session.destroy()

    email = request.form["email"].lower()
    email_again = request.form["email_again"].lower()
    password = request.form["password"]
    password_again = request.form["password_again"]

    errors = {}
    if email.endswith("@cam.ac.uk"):
        email_again = ""
        errors["should_use_raven"] = True
    elif not email_regex.match(email):
        email_again = ""
        errors["email_invalid"] = True
    elif email != email_again:
        email_again = ""
        errors["email_again_bad"] = True

    if len(password) < 8:
        errors["password_short"] = True
    elif password != password_again:
        errors["password_again_bad"] = True

    if not errors:
        with utils.with_savepoint("email_unique") as rollback:
            try:
                # the link advertises password signup as for alumni
                user = users.new_password_user(email, password, 'alumnus')
            except users.EmailAlreadyExists:
                rollback()
                errors["email_already_exists"] = True
            except users.ShouldUseRaven:
                errors["should_use_raven"] = True

    if errors:
        return render_template("login/password-signup.html",
                               email=email, email_again=email_again, **errors)
    else:
        session.create(user)
        return redirect(login_next())
def raven_response():
    if session.ok:
        session.destroy()

    try:
        r = raven.Response(request.args["WLS-Response"])
    except ValueError as e:
        logger.warning("Invalid raven response: %s", e)
        abort(400)

    if r.url != url_for(".raven_response", _external=True):
        logger.warning("Invalid raven response: bad url")
        abort(400)

    issue_delta = (datetime.utcnow() - r.issue).total_seconds()
    if not -5 < issue_delta < 15:
        logger.warning("Invalid raven response: bad issue")
        return redirect(url_for(".raven_error", reason="error"))

    if not r.success:
        if r.status == ucam_webauth.STATUS_CANCELLED:
            return redirect(url_for(".raven_error", reason="cancelled"))
        else:
            return redirect(url_for(".raven_error", reason="error"))

    if "current" not in r.ptags:
        return redirect(url_for(".raven_error", reason="alumni"))

    user = users.get_raven_user(r.principal)

    if user is None:
        # end the transaction: it's safe to do so, and the lookup
        # operation is slow
        postgres.commit()

        try:
            lookup_data = lookup.get_crsid(r.principal)
        except lookup.LookupFailed:
            # lookup logs its own errors
            lookup_data = {}

        # This could happen, I guess...
        if lookup_data.get("person_type") == "alumnus":
            logger.warning("%s has current ptag but lookup yielded "
                           "person_type=alumnus", r.principal)
            return redirect(url_for(".raven_error", reason="alumni"))

        # ensure that the .rollback() in except: doesn't destroy anything
        # (if we couldn't end the transaction above we could use savepoints)
        assert postgres.connection.get_transaction_status() == \
                psycopg2.extensions.TRANSACTION_STATUS_IDLE

        try:
            user = users.new_raven_user(r.principal, r.ptags, lookup_data)
        except users.CRSIDAlreadyExists:
            logger.warning("Raced with another request to create Raven user "
                           "for crsid %s", r.principal)
            postgres.connection.rollback()

            user = users.get_raven_user(r.principal)
            assert user is not None

    # for a disabled password user, we reject it during authentication
    # (in users.password_login). For Raven, we'll have to do it here.
    if not user["enable_login"]:
        return redirect(url_for(".raven_error", reason="disabled"))

    logger.info("logging in user %s (via Raven %s)",
                user["user_id"], r.principal)

    session.create(user)
    return redirect(login_next())