Пример #1
0
def _error_page(message: str, sentry_capture: bool = False, log: str = None):
    """return a error page with a message. if sentry is available, tell user that they can report the problem."""
    sentry_param = {}
    if sentry_capture and plugin_available("sentry"):
        sentry.captureException()
        sentry_param.update({
            "event_id": g.sentry_event_id,
            "public_dsn": sentry.client.get_public_dsn('https')
        })
    if log:
        logger.info(log)
    return render_template('common/error.html',
                           message=message,
                           **sentry_param)
Пример #2
0
def register_by_email_token_check(token: str) -> str:
    """检查邮件验证token有效性,并返回verification requestID"""
    with tracer.trace('verify_email_token'):
        rpc_result = Auth.verify_email_token(token=token)

    if not rpc_result.success:
        raise InvalidTokenError

    request = VerificationRequest.find_by_id(uuid.UUID(rpc_result.request_id))
    if not request:
        logger.error(
            f"can not find related verification request of email token {token}"
        )
    if request.status != VerificationRequest.STATUS_TKN_PASSED:
        request.set_status_token_passed()

    student_id = request.identifier
    if user_exist(student_id):
        logger.info(
            f"User {student_id} try to register again by email token. Request filtered."
        )
        raise AlreadyRegisteredError

    return rpc_result.request_id
Пример #3
0
    def __init__(self, campus: str, building: str, date: datetime.date,
                 time: str):
        _, week, day = get_semester_date(date)

        self.date = f"{date.year}-{date.month}-{date.day}"

        resp = Entity.get_available_rooms(week, f"{day + 1}{time}", campus,
                                          building)
        logger.info(
            f"get available rooms for week={week}, session={day + 1}{time}, campus={campus}, building={building}"
        )

        self.rooms: List[Room] = []
        for r in resp:
            # 反馈占用计数
            feedback_cnt = redis.get(
                f"{redis_prefix}:avail_room_occupy_fb:{week}:{day}:{time}:{r['code']}"
            )

            self.rooms.append(
                Room.make(name=r['name'],
                          room_id=r['code'],
                          feedback_cnt=int(feedback_cnt.decode())
                          if feedback_cnt else 0))
Пример #4
0
def query():
    """
    All in one 搜索入口,可以查询学生、老师、教室,然后跳转到具体资源页面

    正常情况应该是 post 方法,但是也兼容 get 防止意外情况,提高用户体验

    埋点:
    - `query_resource_type`, 查询的资源类型: classroom, single_student, single_teacher, multiple_people, or not_exist.
    - `query_type`, 查询方式(姓名、学工号): by_name, by_id, other
    """

    # if under maintenance, return to maintenance.html
    if app.config["MAINTENANCE"]:
        return render_template("maintenance.html")

    keyword = request.values.get('id')

    if not keyword or len(keyword) < 2:
        flash('请输入需要查询的姓名、学号、教工号或教室名称,长度不要小于2个字符')
        return redirect(url_for('main.main'))

    # 调用 api-server 搜索
    with elasticapm.capture_span('rpc_search'):
        try:
            rpc_result = APIServer.search(keyword)
        except Exception as e:
            return handle_exception_with_error_page(e)

    # 不同类型渲染不同模板
    if len(rpc_result.classrooms) >= 1:  # 优先展示教室
        # 我们在 kibana 中使用服务名过滤 apm 文档,所以 tag 不用增加服务名前缀
        elasticapm.tag(query_resource_type='classroom')
        elasticapm.tag(query_type='by_name')

        if len(rpc_result.classrooms) > 1:  # 多个教室选择
            return render_template('query/multipleClassroomChoice.html',
                                   name=keyword,
                                   classrooms=rpc_result.classrooms)
        return redirect('/classroom/{}/{}'.format(
            rpc_result.classrooms[0].room_id_encoded,
            rpc_result.classrooms[0].semesters[-1]))
    elif len(rpc_result.students) == 1 and len(
            rpc_result.teachers) == 0:  # 一个学生
        elasticapm.tag(query_resource_type='single_student')
        if contains_chinese(keyword):
            elasticapm.tag(query_type='by_name')
        else:
            elasticapm.tag(query_type='by_id')

        if len(rpc_result.students[0].semesters) < 1:
            flash('没有可用学期')
            return redirect(url_for('main.main'))

        return redirect('/student/{}/{}'.format(
            rpc_result.students[0].student_id_encoded,
            rpc_result.students[0].semesters[-1]))
    elif len(rpc_result.teachers) == 1 and len(
            rpc_result.students) == 0:  # 一个老师
        elasticapm.tag(query_resource_type='single_teacher')
        if contains_chinese(keyword):
            elasticapm.tag(query_type='by_name')
        else:
            elasticapm.tag(query_type='by_id')

        if len(rpc_result.teachers[0].semesters) < 1:
            flash('没有可用学期')
            return redirect(url_for('main.main'))

        return redirect('/teacher/{}/{}'.format(
            rpc_result.teachers[0].teacher_id_encoded,
            rpc_result.teachers[0].semesters[-1]))
    elif len(rpc_result.teachers) >= 1 or len(rpc_result.students) >= 1:
        # multiple students, multiple teachers, or mix of both
        elasticapm.tag(query_resource_type='multiple_people')
        if contains_chinese(keyword):
            elasticapm.tag(query_type='by_name')
        else:
            elasticapm.tag(query_type='by_id')

        return render_template('query/peopleWithSameName.html',
                               name=keyword,
                               students=rpc_result.students,
                               teachers=rpc_result.teachers)
    else:
        logger.info("No result for user search",
                    {"keyword": request.values.get('id')})
        elasticapm.tag(query_resource_type='not_exist')
        elasticapm.tag(query_type='other')
        flash('没有找到任何有关 {} 的信息,如果你认为这不应该发生,请联系我们。'.format(
            escape(request.values.get('id'))))
        return redirect(url_for('main.main'))
Пример #5
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')
Пример #6
0
def ics_download(calendar_token: str):
    """
    iCalendar ics 文件下载

    2019-8-25 改为预先缓存文件而非每次动态生成,降低 CPU 压力。如果一小时内两次访问则强刷缓存。
    """
    import time
    from everyclass.server import statsd

    if not is_valid_uuid(calendar_token):
        return 'invalid calendar token', 404

    result = CalendarToken.find_calendar_token(token=calendar_token)
    if not result:
        return 'invalid calendar token', 404
    CalendarToken.update_last_used_time(calendar_token)

    cal_dir = calendar_dir()
    cal_filename = f"{result['type']}_{result['identifier']}_{result['semester']}.ics"
    cal_full_path = os.path.join(cal_dir, cal_filename)
    # 有缓存、且缓存时间小于一天,且不用强刷缓存
    if os.path.exists(cal_full_path) \
            and time.time() - os.path.getmtime(cal_full_path) < SECONDS_IN_ONE_DAY \
            and Redis.calendar_token_use_cache(calendar_token):
        logger.info("ics cache hit")
        statsd.increment("calendar.ics.cache.hit")
        return send_from_directory(cal_dir,
                                   cal_filename,
                                   as_attachment=True,
                                   mimetype='text/calendar')
    statsd.increment("calendar.ics.cache.miss")

    # 无缓存、或需要强刷缓存
    with tracer.trace('rpc'):
        # 获得原始学号或教工号
        if result['type'] == 'student':
            rpc_result = Entity.get_student_timetable(result['identifier'],
                                                      result['semester'])
        else:
            # teacher
            rpc_result = Entity.get_teacher_timetable(result['identifier'],
                                                      result['semester'])

        semester = Semester(result['semester'])

        cards: Dict[Tuple[int, int], List[Dict]] = defaultdict(list)
        for card in rpc_result.cards:
            cards[lesson_string_to_tuple(card.lesson)].append(
                dict(name=card.name,
                     teacher=teacher_list_to_name_str(card.teachers),
                     week=card.weeks,
                     week_string=card.week_string,
                     classroom=card.room,
                     cid=card.card_id_encoded))

    ics_generator.generate(name=rpc_result.name,
                           cards=cards,
                           semester=semester,
                           filename=cal_filename)

    return send_from_directory(cal_dir,
                               cal_filename,
                               as_attachment=True,
                               mimetype='text/calendar')
Пример #7
0
def _return_string(status_code, string, sentry_capture=False, log=None):
    if sentry_capture and plugin_available("sentry"):
        sentry.captureException()
    if log:
        logger.info(log)
    return string, status_code
Пример #8
0
def query():
    """
    All in one 搜索入口,可以查询学生、老师、教室,然后跳转到具体资源页面

    正常情况应该是 post 方法,但是也兼容 get 防止意外情况,提高用户体验

    埋点:
    - `query_resource_type`, 查询的资源类型: classroom, single_student, single_teacher, multiple_people, or not_exist.
    - `query_type`, 查询方式(姓名、学工号): by_name, by_id, other
    """

    # if under maintenance, return to maintenance.html
    if app.config["MAINTENANCE"]:
        return render_template("maintenance.html")

    keyword = request.values.get('id')

    # todo 简拼积分制
    if not keyword or len(keyword) < 2:
        flash('请输入需要查询的姓名、学号、教工号或教室名称,长度不要小于2个字符')
        return redirect(url_for('main.main'))

    # 调用 api-server 搜索
    with tracer.trace('rpc_search'):
        try:
            rpc_result = entity_service.search(keyword)
        except Exception as e:
            return handle_exception_with_error_page(e)

    # 不同类型渲染不同模板
    if len(rpc_result.classrooms) >= 1:  # 优先展示教室
        # 我们在 kibana 中使用服务名过滤 apm 文档,所以 tag 不用增加服务名前缀
        tracer.current_root_span().set_tag("query_resource_type", "classroom")
        tracer.current_root_span().set_tag("query_type", "by_name")

        if len(rpc_result.classrooms) > 1:  # 多个教室选择
            return render_template('entity/multipleClassroomChoice.html',
                                   name=keyword,
                                   classrooms=rpc_result.classrooms)
        return redirect(
            url_for("query.get_classroom",
                    url_rid=rpc_result.classrooms[0].room_id_encoded,
                    url_semester=rpc_result.classrooms[0].semesters[-1] if rpc_result.classrooms[0].semesters else URL_EMPTY_SEMESTER))
    elif len(rpc_result.students) == 1 and len(rpc_result.teachers) == 0:  # 一个学生
        tracer.current_root_span().set_tag("query_resource_type", "single_student")
        if contains_chinese(keyword):
            tracer.current_root_span().set_tag("query_type", "by_name")
        else:
            tracer.current_root_span().set_tag("query_type", "by_id")

        if len(rpc_result.students[0].semesters) < 1:
            flash('没有可用学期')
            return redirect(url_for('main.main'))

        return redirect(
            url_for("query.get_student",
                    url_sid=rpc_result.students[0].student_id_encoded,
                    url_semester=rpc_result.students[0].semesters[-1] if rpc_result.students[0].semesters else URL_EMPTY_SEMESTER))
    elif len(rpc_result.teachers) == 1 and len(rpc_result.students) == 0:  # 一个老师
        tracer.current_root_span().set_tag("query_resource_type", "single_teacher")
        if contains_chinese(keyword):
            tracer.current_root_span().set_tag("query_type", "by_name")
        else:
            tracer.current_root_span().set_tag("query_type", "by_id")

        if len(rpc_result.teachers[0].semesters) < 1:
            flash('没有可用学期')
            return redirect(url_for('main.main'))

        return redirect(
            url_for("query.get_teacher",
                    url_tid=rpc_result.teachers[0].teacher_id_encoded,
                    url_semester=rpc_result.teachers[0].semesters[-1] if rpc_result.teachers[0].semesters else URL_EMPTY_SEMESTER))
    elif len(rpc_result.teachers) >= 1 or len(rpc_result.students) >= 1:
        # multiple students, multiple teachers, or mix of both
        tracer.current_root_span().set_tag("query_resource_type", "multiple_people")

        if contains_chinese(keyword):
            tracer.current_root_span().set_tag("query_type", "by_name")

        else:
            tracer.current_root_span().set_tag("query_type", "by_id")

        students = []
        student_filtered = False
        for student in rpc_result.students:
            if len(student.klass) >= 6 and student.klass[-4:].isdigit() and int(student.klass[-4:-2]) <= 12:
                # 过滤<13级的学生
                student_filtered = True
                continue
            students.append(student)
        # todo 找校友功能
        return render_template('entity/peopleWithSameName.html',
                               name=keyword,
                               students=students,
                               teachers=rpc_result.teachers,
                               student_filtered=student_filtered)
    else:
        logger.info("No result for user search", extra={"keyword": request.values.get('id')})
        tracer.current_root_span().set_tag("query_resource_type", "not_exist")
        tracer.current_root_span().set_tag("query_type", "other")

        flash('没有找到任何有关 {} 的信息,如果你认为这不应该发生,请联系我们。'.format(escape(request.values.get('id'))))
        return redirect(url_for('main.main'))