Ejemplo n.º 1
0
 def get_student(cls, sid_orig: str) -> Optional[StudentSession]:
     """从 Redis 中获取学生信息,有则返回 StudentSession 对象,无则返回 None"""
     res = redis.get("{}:stu:{}".format(cls.prefix, sid_orig))
     if res:
         name, sid = res.decode().split(",")
         return StudentSession(sid_orig=sid_orig, sid=sid, name=name)
     else:
         return None
Ejemplo n.º 2
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"})
Ejemplo n.º 3
0
def _session_save_student_to_register_(student_id: str):
    # 将需要注册的用户并保存到 SESSION_STUDENT_TO_REGISTER
    with elasticapm.capture_span('rpc_get_student'):
        try:
            student = APIServer.get_student(student_id)
        except Exception as e:
            return handle_exception_with_error_page(e)

    session[SESSION_STUDENT_TO_REGISTER] = StudentSession(sid_orig=student.student_id,
                                                          sid=student.student_id_encoded,
                                                          name=student.name)
Ejemplo n.º 4
0
def get_student(url_sid: str, url_semester: str):
    """学生查询"""
    # decrypt identifier in URL
    try:
        _, student_id = decrypt(url_sid, resource_type='student')
    except ValueError:
        return render_template("common/error.html",
                               message=MSG_INVALID_IDENTIFIER)

    # RPC 获得学生课表
    with elasticapm.capture_span('rpc_get_student_timetable'):
        try:
            student = APIServer.get_student_timetable(student_id, url_semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

    # save sid_orig to session for verifying purpose
    # must be placed before privacy level check. Otherwise a registered user could be redirected to register page.
    session[SESSION_LAST_VIEWED_STUDENT] = StudentSession(
        sid_orig=student.student_id,
        sid=student.student_id_encoded,
        name=student.name)

    # 权限检查,如果没有权限则返回
    has_permission, return_val = check_permission(student)
    if not has_permission:
        return return_val

    with elasticapm.capture_span('process_rpc_result'):
        cards: Dict[Tuple[int, int], List[Dict[str, str]]] = dict()
        for card in student.cards:
            day, time = lesson_string_to_tuple(card.lesson)
            if (day, time) not in cards:
                cards[(day, time)] = list()
            cards[(day, time)].append(card)
        empty_5, empty_6, empty_sat, empty_sun = _empty_column_check(cards)
        available_semesters = semester_calculate(url_semester,
                                                 sorted(student.semesters))

    # 增加访客记录
    Redis.add_visitor_count(student.student_id,
                            session.get(SESSION_CURRENT_USER, None))

    return render_template('query/student.html',
                           student=student,
                           cards=cards,
                           empty_sat=empty_sat,
                           empty_sun=empty_sun,
                           empty_6=empty_6,
                           empty_5=empty_5,
                           available_semesters=available_semesters,
                           current_semester=url_semester)
Ejemplo n.º 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"))
Ejemplo n.º 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')
Ejemplo n.º 7
0
def get_student(url_sid: str, url_semester: str):
    """学生查询"""
    from everyclass.server.db.dao import PrivacySettings, VisitTrack, Redis
    from everyclass.server.utils import lesson_string_to_tuple
    from everyclass.server.rpc.api_server import APIServer
    from everyclass.server.utils.resource_identifier_encrypt import decrypt
    from everyclass.server.consts import MSG_INVALID_IDENTIFIER
    from everyclass.server.utils import semester_calculate
    from everyclass.server.consts import SESSION_LAST_VIEWED_STUDENT, SESSION_CURRENT_USER

    # decrypt identifier in URL
    try:
        _, student_id = decrypt(url_sid, resource_type='student')
    except ValueError:
        return render_template("common/error.html",
                               message=MSG_INVALID_IDENTIFIER)

    # RPC to get student timetable
    with elasticapm.capture_span('rpc_get_student_timetable'):
        try:
            student = APIServer.get_student_timetable(student_id, url_semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

    # save sid_orig to session for verifying purpose
    # must be placed before privacy level check. Otherwise a registered user could be redirected to register page.
    session[SESSION_LAST_VIEWED_STUDENT] = StudentSession(
        sid_orig=student.student_id,
        sid=student.student_id_encoded,
        name=student.name)

    # get privacy level, if current user has no permission to view, return now
    with elasticapm.capture_span('get_privacy_settings'):
        privacy_level = PrivacySettings.get_level(student.student_id)

    # 仅自己可见、且未登录或登录用户非在查看的用户,拒绝访问
    if privacy_level == 2 and (
            not session.get(SESSION_CURRENT_USER, None)
            or session[SESSION_CURRENT_USER].sid_orig != student.student_id):
        return render_template('query/studentBlocked.html',
                               name=student.name,
                               falculty=student.deputy,
                               class_name=student.klass,
                               level=2)
    # 实名互访
    if privacy_level == 1:
        # 未登录,要求登录
        if not session.get(SESSION_CURRENT_USER, None):
            return render_template('query/studentBlocked.html',
                                   name=student.name,
                                   falculty=student.deputy,
                                   class_name=student.klass,
                                   level=1)
        # 仅自己可见的用户访问实名互访的用户,拒绝,要求调整自己的权限
        if PrivacySettings.get_level(
                session[SESSION_CURRENT_USER].sid_orig) == 2:
            return render_template('query/studentBlocked.html',
                                   name=student.name,
                                   falculty=student.deputy,
                                   class_name=student.klass,
                                   level=3)

    with elasticapm.capture_span('process_rpc_result'):
        cards: Dict[Tuple[int, int], List[Dict[str, str]]] = dict()
        for card in student.cards:
            day, time = lesson_string_to_tuple(card.lesson)
            if (day, time) not in cards:
                cards[(day, time)] = list()
            cards[(day, time)].append(card)
        empty_5, empty_6, empty_sat, empty_sun = _empty_column_check(cards)
        available_semesters = semester_calculate(url_semester,
                                                 sorted(student.semesters))

    # 公开或实名互访模式、已登录、不是自己访问自己,则留下轨迹
    if privacy_level != 2 and \
            session.get(SESSION_CURRENT_USER, None) and \
            session[SESSION_CURRENT_USER].sid_orig != session[SESSION_LAST_VIEWED_STUDENT].sid_orig:
        VisitTrack.update_track(host=student.student_id,
                                visitor=session[SESSION_CURRENT_USER])

    # 增加访客记录
    Redis.add_visitor_count(student.student_id,
                            session.get(SESSION_CURRENT_USER, None))

    return render_template('query/student.html',
                           student=student,
                           cards=cards,
                           empty_sat=empty_sat,
                           empty_sun=empty_sun,
                           empty_6=empty_6,
                           empty_5=empty_5,
                           available_semesters=available_semesters,
                           current_semester=url_semester)
Ejemplo n.º 8
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')