Ejemplo n.º 1
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.
    """
    # 检查 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 = Entity.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 = Entity.get_student_timetable(res_id, semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

        with tracer.trace('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))
Ejemplo n.º 2
0
def legacy_get_ics(student_id, semester_str):
    """
    早期 iCalendar 订阅端点,出于兼容性考虑保留,仅支持未设定隐私等级的学生,其他情况使用新的日历订阅令牌获得 ics 文件。
    """
    # fix parameters
    place = student_id.find('-')
    semester_str = student_id[place + 1:len(student_id)] + '-' + semester_str
    student_id = student_id[:place]

    semester = Semester(semester_str)

    search_result = Entity.search(student_id)

    if len(search_result.students) != 1:
        # bad request
        return abort(400)

    if semester.to_str() not in search_result.students[0].semesters:
        return abort(400)

    with tracer.trace('get_privacy_settings'):
        privacy_settings = PrivacySettings.get_level(
            search_result.students[0].student_id)

    if privacy_settings != 0:
        # force user to get a calendar token when the user is privacy-protected but accessed through legacy interface
        return "Visit {} to get your calendar".format(
            url_for("main.main", _external=True)), 401
    else:
        token = CalendarToken.get_or_set_calendar_token(
            resource_type="student",
            identifier=search_result.students[0].student_id,
            semester=semester.to_str())
        return redirect(url_for('calendar.ics_download', calendar_token=token))
Ejemplo n.º 3
0
def get_classroom(url_rid, url_semester):
    """教室查询"""
    # decrypt identifier in URL
    try:
        _, room_id = decrypt(url_rid, resource_type='room')
    except ValueError:
        return render_template("common/error.html",
                               message=MSG_INVALID_IDENTIFIER)

    # RPC to get classroom timetable
    with tracer.trace('rpc_get_classroom_timetable'):
        try:
            room = Entity.get_classroom_timetable(url_semester, room_id)
        except Exception as e:
            return handle_exception_with_error_page(e)

    with tracer.trace('process_rpc_result'):
        cards = defaultdict(list)
        for card in room.cards:
            day, time = lesson_string_to_tuple(card.lesson)
            cards[(day, time)].append(card)

    empty_5, empty_6, empty_sat, empty_sun = _empty_column_check(cards)

    available_semesters = semester_calculate(url_semester, room.semesters)

    return render_template('query/room.html',
                           room=room,
                           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.º 4
0
    def get_visitors(cls, sid_orig: str) -> List[Dict]:
        """获得访客列表"""
        from everyclass.rpc.entity import Entity

        with pg_conn_context() as conn, conn.cursor() as cursor:
            select_query = """
            SELECT visitor_id, last_visit_time FROM visit_tracks where host_id=%s ORDER BY last_visit_time DESC;
            """
            cursor.execute(select_query, (sid_orig, ))
            result = cursor.fetchall()
            conn.commit()

        visitor_list = []
        for record in result:
            # query api-server
            search_result = Entity.search(record[0])

            visitor_list.append({
                "name":
                search_result.students[0].name,
                "student_id":
                search_result.students[0].student_id_encoded,
                "last_semester":
                search_result.students[0].semesters[-1],
                "visit_time":
                record[1]
            })
        return visitor_list
Ejemplo n.º 5
0
def cal_page(url_res_type: str, url_res_identifier: str, url_semester: str):
    """课表导出页面视图函数"""
    # 检查 URL 参数
    try:
        res_type, res_id = decrypt(url_res_identifier)
    except ValueError:
        return render_template("common/error.html",
                               message=MSG_INVALID_IDENTIFIER)
    if url_res_type not in ('student', 'teacher') or url_res_type != res_type:
        return render_template("common/error.html", message=MSG_400)

    if url_res_type == 'student':
        try:
            student = Entity.get_student_timetable(res_id, url_semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

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

        token = CalendarToken.get_or_set_calendar_token(
            resource_type=url_res_type,
            identifier=student.student_id,
            semester=url_semester)
    else:
        try:
            teacher = Entity.get_teacher_timetable(res_id, url_semester)
        except Exception as e:
            return handle_exception_with_error_page(e)

        token = CalendarToken.get_or_set_calendar_token(
            resource_type=url_res_type,
            identifier=teacher.teacher_id,
            semester=url_semester)

    ics_url = url_for('calendar.ics_download',
                      calendar_token=token,
                      _external=True)
    ics_webcal = ics_url.replace('https', 'webcal').replace('http', 'webcal')

    return render_template('calendarSubscribe.html',
                           ics_url=ics_url,
                           ics_webcal=ics_webcal,
                           android_client_url=app.config['ANDROID_CLIENT_URL'])
Ejemplo n.º 6
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.º 7
0
def is_taking(cotc: Dict) -> bool:
    """检查当前用户是否选了这门课"""
    user_is_taking = False

    if session.get(SESSION_CURRENT_USER, None):
        # 检查当前用户是否选了这门课
        student = Entity.get_student(session[SESSION_CURRENT_USER].sid_orig)
        for semester in sorted(student.semesters,
                               reverse=True):  # 新学期可能性大,学期从新到旧查找
            timetable = Entity.get_student_timetable(
                session[SESSION_CURRENT_USER].sid_orig, semester)
            for card in timetable.cards:
                if card.course_id == cotc["course_id"] and cotc[
                        "teacher_id_str"] == teacher_list_to_tid_str(
                            card.teachers):
                    user_is_taking = True
                    break
            if user_is_taking:
                break
    return user_is_taking
Ejemplo n.º 8
0
def edit_review(cotc_id: int):
    """进行评价"""
    cotc_id = int(cotc_id)
    cotc = COTeachingClass.get_doc(cotc_id)
    if not cotc:
        return render_template('common/error.html', message=MSG_404)

    if not is_taking(cotc):
        return render_template('common/error.html', message=MSG_NOT_IN_COURSE)

    if request.method == 'GET':  # 展示表单页面
        doc = CourseReview.get_my_review(
            cotc_id=cotc_id,
            student_id=session[SESSION_CURRENT_USER].sid_orig)  # 已经评分
        if doc:
            my_rating = doc["rate"]
            my_review = doc["review"]
        else:
            my_rating = 0
            my_review = ""
        return render_template("course_review/add_review.html",
                               cotc=cotc,
                               my_rating=my_rating,
                               my_review=my_review)
    else:  # 表单提交
        if not request.form.get("rate",
                                None) or request.form["rate"] not in map(
                                    str, (1, 2, 3, 4, 5)):
            flash("请填写正确的评分")
            return redirect(
                url_for("course_review.edit_review", cotc_id=cotc_id))
        if not request.form.get("review", None):
            flash("请填写评价")
            return redirect(
                url_for("course_review.edit_review", cotc_id=cotc_id))
        if len(request.form["review"]) > 200:
            flash("评论不要超过200个字符")
            return redirect(
                url_for("course_review.edit_review", cotc_id=cotc_id))

        try:
            student = Entity.get_student(
                session[SESSION_CURRENT_USER].sid_orig)
        except Exception as e:
            return handle_exception_with_error_page(e)

        fuzzy_name = student.klass + "学生"

        CourseReview.edit_my_review(cotc_id,
                                    session[SESSION_CURRENT_USER].sid_orig,
                                    int(request.form["rate"]),
                                    escape(request.form["review"]), fuzzy_name)
        flash("评分成功。")
        return redirect(url_for("course_review.show_review", cotc_id=cotc_id))
Ejemplo n.º 9
0
def _session_save_student_to_register_(student_id: str):
    # 将需要注册的用户并保存到 SESSION_STUDENT_TO_REGISTER
    with tracer.trace('rpc_get_student'):
        try:
            student = Entity.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.º 10
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 tracer.trace('rpc_get_student_timetable'):
        try:
            student = Entity.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 tracer.trace('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.º 11
0
def main():
    """用户主页"""
    try:
        student = Entity.get_student(session[SESSION_CURRENT_USER].sid_orig)
    except Exception as e:
        return handle_exception_with_error_page(e)

    return render_template(
        'user/main.html',
        name=session[SESSION_CURRENT_USER].name,
        student_id_encoded=session[SESSION_CURRENT_USER].sid,
        last_semester=student.semesters[-1] if student.semesters else None,
        privacy_level=PrivacySettings.get_level(
            session[SESSION_CURRENT_USER].sid_orig))
Ejemplo n.º 12
0
def android_client_get_semester(identifier):
    """android client get a student or teacher's semesters
    """
    try:
        search_result = Entity.search(identifier)
    except Exception as e:
        return handle_exception_with_error_page(e)

    if len(search_result.students) == 1:
        return jsonify({
            'type': 'student',
            'sid': search_result.students[0].student_id_encoded,
            'semesters': search_result.students[0].semesters
        })
    if len(search_result.teachers) == 1:
        return jsonify({
            'type': 'teacher',
            'tid': search_result.teachers[0].teacher_id_encoded,
            'semesters': search_result.teachers[0].semesters
        })
    return "Bad request (got multiple people)", 400
Ejemplo n.º 13
0
def get_card(url_cid: str, url_semester: str):
    """课程查询"""
    # decrypt identifier in URL
    try:
        _, card_id = decrypt(url_cid, resource_type='klass')
    except ValueError:
        return render_template("common/error.html",
                               message=MSG_INVALID_IDENTIFIER)

    # RPC to get card
    with tracer.trace('rpc_get_card'):
        try:
            card = Entity.get_card(url_semester, card_id)
        except Exception as e:
            return handle_exception_with_error_page(e)

    day, time = lesson_string_to_tuple(card.lesson)

    # 给“文化素质类”等加上“课”后缀
    if card.type and card.type[-1] != '课':
        card.type = card.type + '课'

    # cotc_id = COTeachingClass.get_id_by_card(card)
    # course_review_doc = CourseReview.get_review(cotc_id)

    return render_template(
        'query/card.html',
        card=card,
        card_day=get_day_chinese(day),
        card_time=get_time_chinese(time),
        show_union_class=not card.union_name.isdigit(),  # 合班名称为数字时不展示合班名称
        # cotc_id=cotc_id,
        # cotc_rating=course_review_doc["avg_rate"],
        cotc_id=0,
        cotc_rating=0,
        current_semester=url_semester)
Ejemplo n.º 14
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))
Ejemplo n.º 15
0
def get_student(student_id: str):
    return Entity.get_student(student_id)
Ejemplo n.º 16
0
def get_rooms():
    return AllRooms.make(Entity.get_rooms())
Ejemplo n.º 17
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 tracer.trace('rpc_search'):
        try:
            rpc_result = Entity.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('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:  # 一个学生
        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('/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:  # 一个老师
        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('/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
        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")

        return render_template('query/peopleWithSameName.html',
                               name=keyword,
                               students=rpc_result.students,
                               teachers=rpc_result.teachers)
    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'))
Ejemplo n.º 18
0
def get_student_timetable(student_id: str, semester: str):
    return Entity.get_student_timetable(student_id, semester)
Ejemplo n.º 19
0
def get_teacher(teacher_id: str):
    return Entity.get_teacher(teacher_id)
Ejemplo n.º 20
0
def create_app() -> Flask:
    """创建 flask app"""
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.consts import MSG_INTERNAL_ERROR
    from everyclass.server.utils import plugin_available

    app = Flask(__name__,
                static_folder='../../frontend/dist',
                static_url_path='',
                template_folder="../../frontend/templates")

    # load app config
    from everyclass.server.config import get_config
    _config = get_config()
    app.config.from_object(_config)  # noqa: T484
    """
    每课统一日志机制


    规则如下:
    - DEBUG 模式下会输出 DEBUG 等级的日志,否则输出 INFO 及以上等级的日志
    - 日志为 JSON 格式,会被节点上的 agent 采集并发送到 datadog,方便结合 metrics 和 APM 数据分析
    - WARNING 以上级别的输出到 Sentry 做错误聚合


    日志等级:
    critical – for errors that lead to termination
    error – for errors that occur, but are handled
    warning – for exceptional circumstances that might not be errors
    notice – for non-error messages you usually want to see
    info – for messages you usually don’t want to see
    debug – for debug messages


    Sentry:
    https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    - stack 默认是 False
    """
    if app.config['DEBUG']:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)

    # CDN
    CDN(app)

    # moment
    Moment(app)

    # encrypted session
    app.session_interface = EncryptedSessionInterface()

    # 导入并注册 blueprints
    from everyclass.server.calendar.views import cal_blueprint
    from everyclass.server.query import query_blueprint
    from everyclass.server.views import main_blueprint as main_blueprint
    from everyclass.server.user.views import user_bp
    from everyclass.server.course_review.views import cr_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(user_bp, url_prefix='/user')

    # 初始化 RPC 模块
    from everyclass.server.utils.resource_identifier_encrypt import encrypt
    from everyclass.rpc import init as init_rpc
    from everyclass.rpc.entity import Entity
    from everyclass.rpc.auth import Auth
    init_rpc(resource_id_encrypt_function=encrypt
             )  # 为 everyclass.rpc 模块注入 encrypt 函数
    if 'API_SERVER_BASE_URL' in app.config:
        Entity.set_base_url(app.config['API_SERVER_BASE_URL'])
    if 'API_SERVER_TOKEN' in app.config:
        Entity.set_request_token(app.config['API_SERVER_TOKEN'])
    if 'AUTH_BASE_URL' in app.config:
        Auth.set_base_url(app.config['AUTH_BASE_URL'])

    # course review feature gating
    if app.config['FEATURE_GATING']['course_review']:
        app.register_blueprint(cr_blueprint, url_prefix='/course_review')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 APM 标识用户"""
        from everyclass.server.consts import SESSION_CURRENT_USER

        if not session.get('user_id', None) and request.endpoint not in (
                "main.health_check", "static"):
            logger.info(
                f"Give a new user ID for new user. endpoint: {request.endpoint}"
            )
            session['user_id'] = new_user_id_sequence()
        if session.get('user_id', None):
            tracer.current_root_span().set_tag("user_id",
                                               session['user_id'])  # 唯一用户 ID
        if session.get(SESSION_CURRENT_USER, None):
            tracer.current_root_span().set_tag(
                "username", session[SESSION_CURRENT_USER])  # 学号

    @app.before_request
    def log_request():
        """日志中记录请求"""
        logger.info(f'Request received: {request.method} {request.path}')

    @app.before_request
    def delete_old_session():
        """删除旧的客户端 session(长度过长导致无法在 mongodb 中建立索引)"""
        if request.cookies.get("session") and len(
                request.cookies.get("session")) > 50:
            session.clear()
            return redirect(request.url)

    @app.after_request
    def response_minify(response):
        """用 htmlmin 压缩 HTML,减轻带宽压力"""
        if app.config[
                'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8':
            response.set_data(minify(response.get_data(as_text=True)))
        return response

    @app.template_filter('versioned')
    def version_filter(filename):
        """
        模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css'

        :param filename: 文件名
        :return: 新的文件名
        """
        if app.config['STATIC_VERSIONED']:
            if filename[:4] == 'css/':
                new_filename = app.config['STATIC_MANIFEST'][filename[4:]]
                return 'css/' + new_filename
            elif filename[:3] == 'js/':
                new_filename = app.config['STATIC_MANIFEST'][filename[3:]]
                return new_filename
            else:
                return app.config['STATIC_MANIFEST'][filename]
        return filename

    @app.errorhandler(500)
    def internal_server_error(error):
        if plugin_available("sentry"):
            return render_template(
                'common/error.html',
                message=MSG_INTERNAL_ERROR,
                event_id=g.sentry_event_id,
                public_dsn=sentry.client.get_public_dsn('https'))
        return "<h4>500 Error: {}</h4><br>You are seeing this page because Sentry is not available.".format(
            repr(error))

    global __app
    __app = app

    return app
Ejemplo n.º 21
0
def get_classroom_timetable(semester: str, room_id: str):
    return Entity.get_classroom_timetable(semester, room_id)
Ejemplo n.º 22
0
def get_teacher_timetable(teacher_id: str, semester: str):
    return Entity.get_teacher_timetable(teacher_id, semester)
Ejemplo n.º 23
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.º 24
0
def get_card(semester: str, card_id: str) -> CardResult:
    return Entity.get_card(semester, card_id)
Ejemplo n.º 25
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.º 26
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')
Ejemplo n.º 27
0
from everyclass.rpc import init as _init_rpc
from everyclass.rpc.entity import Entity as _entity
from everyclass.server.utils.config import get_config
from everyclass.server.utils.encryption import encrypt

_init_rpc(resource_id_encrypt_function=encrypt)  # 为 everyclass.rpc 模块注入 encrypt 函数

_entity.set_base_url(get_config().ENTITY_BASE_URL)
_entity.set_request_token(get_config().ENTITY_TOKEN)
Ejemplo n.º 28
0
def search(keyword: str) -> SearchResult:
    return Entity.search(keyword)