Exemple #1
0
def register_by_password_status():
    """AJAX 刷新教务验证状态"""
    if not request.args.get("request", None) or not isinstance(
            request.args["request"], str):
        return "Invalid request"
    req = IdentityVerification.get_request_by_id(request.args.get("request"))
    if not req:
        return "Invalid request"
    if req["verification_method"] != "password":
        logger.warn(
            "Non-password verification request is trying get status from password interface"
        )
        return "Invalid request"

    # fetch status from everyclass-auth
    with tracer.trace('get_result'):
        try:
            rpc_result = Auth.get_result(str(request.args.get("request")))
        except Exception as e:
            return handle_exception_with_error_page(e)
        logger.info(f"RPC result: {rpc_result}")

    if rpc_result.success:  # 密码验证通过,设置请求状态并新增用户
        IdentityVerification.set_request_status(
            str(request.args.get("request")), ID_STATUS_PWD_SUCCESS)

        verification_req = IdentityVerification.get_request_by_id(
            str(request.args.get("request")))

        # 从 api-server 查询学生基本信息
        try:
            student = Entity.get_student(verification_req["sid_orig"])
        except Exception as e:
            return handle_exception_with_error_page(e)

        # 添加用户
        try:
            User.add_user(sid_orig=verification_req["sid_orig"],
                          password=verification_req["password"],
                          password_encrypted=True)
        except ValueError:
            pass  # 已经注册成功,但不知为何进入了中间状态,没有执行下面的删除 session 的代码,并且用户刷新页面

        # write login state to session
        flash(MSG_REGISTER_SUCCESS)
        if SESSION_PWD_VER_REQ_ID in session:
            del session[SESSION_PWD_VER_REQ_ID]
        session[SESSION_CURRENT_USER] = StudentSession(
            sid_orig=student.student_id,
            sid=student.student_id_encoded,
            name=student.name)

        return jsonify({"message": "SUCCESS"})
    elif rpc_result.message in ("PASSWORD_WRONG", "INTERNAL_ERROR",
                                "INVALID_REQUEST_ID"):
        return jsonify({"message": rpc_result.message})
    else:
        return jsonify({"message": "NEXT_TIME"})
Exemple #2
0
def android_client_get_ics(resource_type, identifier, semester):
    """
    android client get a student or teacher's ics file

    If the student does not have privacy mode, anyone can use student number to subscribe his calendar.
    If the privacy mode is on and there is no HTTP basic authentication, return a 401(unauthorized)
    status code and the Android client ask user for password to try again.
    """
    from flask import redirect, url_for, request

    from everyclass.server.db.dao import PrivacySettings, CalendarToken, User
    from everyclass.server.rpc.api_server import APIServer
    from everyclass.server.utils.resource_identifier_encrypt import decrypt

    # 检查 URL 参数
    try:
        res_type, res_id = decrypt(identifier)
    except ValueError:
        return "Invalid identifier", 400
    if resource_type not in ('student',
                             'teacher') or resource_type != res_type:
        return "Unknown resource type", 400

    if resource_type == 'teacher':
        try:
            teacher = APIServer.get_teacher_timetable(res_id, semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

        cal_token = CalendarToken.get_or_set_calendar_token(
            resource_type=resource_type,
            identifier=teacher.teacher_id,
            semester=semester)
        return redirect(
            url_for('calendar.ics_download', calendar_token=cal_token))
    else:  # student
        try:
            student = APIServer.get_student_timetable(res_id, semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

        with elasticapm.capture_span('get_privacy_settings'):
            privacy_level = PrivacySettings.get_level(student.student_id)

        # get authorization from HTTP header and verify password if privacy is on
        if privacy_level != 0:
            if not request.authorization:
                return "Unauthorized (privacy on)", 401
            username, password = request.authorization
            if not User.check_password(username, password):
                return "Unauthorized (password wrong)", 401
            if student.student_id != username:
                return "Unauthorized (username mismatch)", 401

        cal_token = CalendarToken.get_or_set_calendar_token(
            resource_type=resource_type,
            identifier=student.student_id,
            semester=semester)
        return redirect(
            url_for('calendar.ics_download', calendar_token=cal_token))
Exemple #3
0
def register():
    """注册:第一步:输入学号"""
    if request.method == 'GET':
        return render_template('user/register.html')
    else:
        if not request.form.get("xh", None):  # 表单为空
            flash(MSG_EMPTY_USERNAME)
            return redirect(url_for("user.register"))

        _session_save_student_to_register_(request.form.get("xh", None))

        # 如果输入的学号已经注册,跳转到登录页面
        if User.exist(session[SESSION_STUDENT_TO_REGISTER].sid_orig):
            flash(MSG_ALREADY_REGISTERED)
            return redirect(url_for('user.login'))

        return redirect(url_for('user.register_choice'))
Exemple #4
0
def register_by_email():
    """注册:第三步:使用邮箱验证注册"""
    if not session.get(SESSION_STUDENT_TO_REGISTER, None):  # 步骤异常,跳回第一步
        return redirect(url_for('user.register'))

    sid_orig = session[SESSION_STUDENT_TO_REGISTER].sid_orig

    if User.exist(sid_orig):
        return render_template("common/error.html", message=MSG_ALREADY_REGISTERED)

    request_id = IdentityVerification.new_register_request(sid_orig, "email", ID_STATUS_SENT)

    with elasticapm.capture_span('send_email'):
        try:
            rpc_result = Auth.register_by_email(request_id, sid_orig)
        except Exception as e:
            return handle_exception_with_error_page(e)

    if rpc_result['acknowledged']:
        return render_template('user/emailSent.html', request_id=request_id)
    else:
        return render_template('common/error.html', message=MSG_INTERNAL_ERROR)
Exemple #5
0
def login():
    """
    登录页

    判断学生是否未注册,若已经注册,渲染登录页。否则跳转到注册页面。
    """
    if request.method == 'GET':
        if session.get(SESSION_LAST_VIEWED_STUDENT, None):
            user_name = session[SESSION_LAST_VIEWED_STUDENT].name
        else:
            user_name = None

        return render_template('user/login.html', name=user_name)
    else:  # 表单提交
        if not request.form.get("password", None):
            flash(MSG_EMPTY_PASSWORD)
            return redirect(url_for("user.login"))

        # captcha
        if not TencentCaptcha.verify_old():
            flash(MSG_INVALID_CAPTCHA)
            return redirect(url_for("user.login"))

        if request.form.get("xh", None):  # 已手动填写用户名
            student_id = request.form["xh"]

            # 检查学号是否存在
            try:
                Entity.get_student(student_id)
            except RpcResourceNotFound:
                flash(MSG_USERNAME_NOT_EXIST)
                return redirect(url_for("user.login"))
            except Exception as e:
                return handle_exception_with_error_page(e)

        else:
            if session.get(SESSION_LAST_VIEWED_STUDENT, None):
                student_id = session[
                    SESSION_LAST_VIEWED_STUDENT].sid_orig  # 没有手动填写,使用获取最后浏览的学生
            else:
                flash(MSG_EMPTY_USERNAME)  # 没有最后浏览的学生,必须填写用户名
                return redirect(url_for("user.login"))

        try:
            success = User.check_password(student_id, request.form["password"])
        except ValueError:
            # 未注册
            flash(MSG_NOT_REGISTERED)
            _session_save_student_to_register_(student_id)
            return redirect(url_for("user.register"))

        if success:
            try:
                student = Entity.get_student(student_id)
            except Exception as e:
                return handle_exception_with_error_page(e)

            # 登录态写入 session
            session[SESSION_CURRENT_USER] = StudentSession(
                sid_orig=student_id,
                sid=student.student_id_encoded,
                name=student.name)
            return redirect(url_for("user.main"))
        else:
            flash(MSG_WRONG_PASSWORD)
            return redirect(url_for("user.login"))
Exemple #6
0
def email_verification():
    """注册:邮箱验证"""
    if request.method == 'POST':
        # 设置密码表单提交
        if not session.get(SESSION_EMAIL_VER_REQ_ID, None):
            return render_template("common/error.html", message=MSG_400)

        req = IdentityVerification.get_request_by_id(
            session[SESSION_EMAIL_VER_REQ_ID])
        if not req:
            return render_template("common/error.html",
                                   message=MSG_TOKEN_INVALID)

        # 此处不是一定需要验证状态,但是为了保险还是判断一下
        if req["status"] != ID_STATUS_TKN_PASSED:
            return render_template("common/error.html",
                                   message=MSG_TOKEN_INVALID)

        if any(
                map(lambda x: not request.form.get(x, None),
                    ("password", "password2"))):  # check if empty password
            flash(MSG_EMPTY_PASSWORD)
            return redirect(url_for("user.email_verification"))

        if request.form["password"] != request.form["password2"]:
            flash(MSG_PWD_DIFFERENT)
            return redirect(url_for("user.email_verification"))

        sid_orig = req['sid_orig']

        # 密码强度检查
        pwd_strength_report = zxcvbn(password=request.form["password"])
        if pwd_strength_report['score'] < 2:
            SimplePassword.new(password=request.form["password"],
                               sid_orig=sid_orig)
            flash(MSG_WEAK_PASSWORD)
            return redirect(url_for("user.email_verification"))

        try:
            User.add_user(sid_orig=sid_orig, password=request.form['password'])
        except ValueError:
            flash(MSG_ALREADY_REGISTERED)
            logger.info(
                f"User {sid_orig} try to register again by email token. Filtered when posting."
            )
            return redirect(url_for("user.email_verification"))
        del session[SESSION_EMAIL_VER_REQ_ID]
        IdentityVerification.set_request_status(str(req["request_id"]),
                                                ID_STATUS_PASSWORD_SET)
        flash(MSG_REGISTER_SUCCESS)

        # 查询 api-server 获得学生基本信息
        try:
            student = Entity.get_student(sid_orig)
        except Exception as e:
            return handle_exception_with_error_page(e)

        # 登录态写入 session
        session[SESSION_CURRENT_USER] = StudentSession(
            sid_orig=student.student_id,
            sid=student.student_id_encoded,
            name=student.name)
        return redirect(url_for("user.main"))
    else:
        # 设置密码页面
        if not session.get(SESSION_EMAIL_VER_REQ_ID, None):
            if not request.args.get("token", None):
                return render_template("common/error.html", message=MSG_400)

            with tracer.trace('verify_email_token'):
                try:
                    rpc_result = Auth.verify_email_token(
                        token=request.args.get("token", None))
                except Exception as e:
                    return handle_exception_with_error_page(e)

            if rpc_result.success:
                session[SESSION_EMAIL_VER_REQ_ID] = rpc_result.request_id
                IdentityVerification.set_request_status(
                    rpc_result.request_id, ID_STATUS_TKN_PASSED)

                req = IdentityVerification.get_request_by_id(
                    rpc_result.request_id)
                student_id = req["sid_orig"]
                if User.exist(student_id):
                    flash(MSG_ALREADY_REGISTERED)
                    logger.info(
                        f"User {student_id} try to register again by email token. Request filtered."
                    )
                    return redirect(url_for("main.main"))

                return render_template('user/emailVerificationProceed.html')
            else:
                return render_template("common/error.html",
                                       message=MSG_TOKEN_INVALID)
        else:
            # have session
            return render_template('user/emailVerificationProceed.html')
Exemple #7
0
def email_verification():
    """注册:邮箱验证"""
    if request.method == 'POST':
        # 设置密码表单提交
        if not session.get(SESSION_VER_REQ_ID, None):
            return render_template("common/error.html", message=MSG_400)

        req = IdentityVerification.get_request_by_id(session[SESSION_VER_REQ_ID])
        if not req:
            return render_template("common/error.html", message=MSG_TOKEN_INVALID)

        # 由于 SESSION_VER_REQ_ID 在密码验证和邮件验证两个验证方式中共享,当使用密码验证写入了 session 之后,如果马上在邮件验证页面
        # POST,并且此处不做请求状态的判断,将会绕过验证过程直接设置密码
        if req["status"] != ID_STATUS_TKN_PASSED:
            return render_template("common/error.html", message=MSG_TOKEN_INVALID)

        if any(map(lambda x: not request.form.get(x, None), ("password", "password2"))):  # check if empty password
            flash(MSG_EMPTY_PASSWORD)
            return redirect(url_for("user.email_verification"))

        if request.form["password"] != request.form["password2"]:
            flash(MSG_PWD_DIFFERENT)
            return redirect(url_for("user.email_verification"))

        sid_orig = req['sid_orig']

        # 密码强度检查
        pwd_strength_report = zxcvbn(password=request.form["password"])
        if pwd_strength_report['score'] < 2:
            SimplePassword.new(password=request.form["password"], sid_orig=sid_orig)
            flash(MSG_WEAK_PASSWORD)
            return redirect(url_for("user.email_verification"))

        User.add_user(sid_orig=sid_orig, password=request.form['password'])
        del session[SESSION_VER_REQ_ID]
        IdentityVerification.set_request_status(str(req["request_id"]), ID_STATUS_PASSWORD_SET)
        flash(MSG_REGISTER_SUCCESS)

        # 查询 api-server 获得学生基本信息
        try:
            student = APIServer.get_student(sid_orig)
        except Exception as e:
            return handle_exception_with_error_page(e)

        # 登录态写入 session
        session[SESSION_CURRENT_USER] = StudentSession(sid_orig=student.student_id,
                                                       sid=student.student_id_encoded,
                                                       name=student.name)
        return redirect(url_for("user.main"))
    else:
        # 设置密码页面
        if not session.get(SESSION_VER_REQ_ID, None):
            if not request.args.get("token", None):
                return render_template("common/error.html", message=MSG_400)

            with elasticapm.capture_span('verify_email_token'):
                try:
                    rpc_result = Auth.verify_email_token(token=request.args.get("token", None))
                except Exception as e:
                    return handle_exception_with_error_page(e)

            if rpc_result['success']:
                session[SESSION_VER_REQ_ID] = rpc_result['request_id']
                IdentityVerification.set_request_status(rpc_result['request_id'], ID_STATUS_TKN_PASSED)
                return render_template('user/emailVerificationProceed.html')
            else:
                return render_template("common/error.html", message=MSG_TOKEN_INVALID)
        else:
            # have session
            return render_template('user/emailVerificationProceed.html')