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))
def cal_page(url_res_type: str, url_res_identifier: str, url_semester: str): """课表导出页面视图函数""" from flask import current_app as app, render_template, url_for, session from everyclass.server.db.dao import CalendarToken, PrivacySettings from everyclass.server.consts import MSG_400, SESSION_CURRENT_USER, MSG_401 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 # 检查 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 = APIServer.get_student_timetable(res_id, url_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) if privacy_level != 0: if not (session.get(SESSION_CURRENT_USER, None) and session[SESSION_CURRENT_USER].sid_orig == student.student_id): return render_template("common/error.html", message=MSG_401) token = CalendarToken.get_or_set_calendar_token( resource_type=url_res_type, identifier=student.student_id, semester=url_semester) else: try: teacher = APIServer.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'])
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 elasticapm.capture_span('get_result'): try: rpc_result = Auth.get_result(str(request.args.get("request"))) except Exception as e: return handle_exception_with_error_page(e) 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 = APIServer.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"): return jsonify({"message": rpc_result["message"]}) else: return jsonify({"message": "NEXT_TIME"})
def cal_page(url_res_type: str, url_res_identifier: str, url_semester: str): """课表导出页面视图函数""" from flask import current_app as app, render_template, url_for from everyclass.server.db.dao import CalendarToken from everyclass.server.consts import MSG_400 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 # 检查 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 = APIServer.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 = APIServer.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'])
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 elasticapm.capture_span('rpc_get_classroom_timetable'): try: room = APIServer.get_classroom_timetable(url_semester, room_id) except Exception as e: return handle_exception_with_error_page(e) with elasticapm.capture_span('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)
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 elasticapm.capture_span('rpc_get_card'): try: card = APIServer.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"], current_semester=url_semester)
def register_by_password(): """注册:第三步:使用密码验证注册""" if request.method == 'POST': if any( map(lambda x: not request.form.get(x, None), ("password", "password2", "jwPassword"))): flash(MSG_EMPTY_PASSWORD) return redirect(url_for("user.register_by_password")) # 密码强度检查 pwd_strength_report = zxcvbn(password=request.form["password"]) if pwd_strength_report['score'] < 2: SimplePassword.new( password=request.form["password"], sid_orig=session[SESSION_STUDENT_TO_REGISTER].sid_orig) flash(MSG_WEAK_PASSWORD) return redirect(url_for("user.register_by_password")) if request.form["password"] != request.form["password2"]: flash(MSG_PWD_DIFFERENT) return redirect(url_for("user.register_by_password")) # captcha if not TencentCaptcha.verify(): flash(MSG_INVALID_CAPTCHA) return redirect(url_for("user.register_by_password")) request_id = IdentityVerification.new_register_request( session[SESSION_STUDENT_TO_REGISTER].sid_orig, "password", ID_STATUS_WAIT_VERIFY, password=request.form["password"]) # call everyclass-auth to verify password with elasticapm.capture_span('register_by_password'): try: rpc_result = Auth.register_by_password( request_id=str(request_id), student_id=session[SESSION_STUDENT_TO_REGISTER].sid_orig, password=request.form["jwPassword"]) except Exception as e: return handle_exception_with_error_page(e) if rpc_result['acknowledged']: session[SESSION_PWD_VER_REQ_ID] = request_id return render_template('user/passwordRegistrationPending.html', request_id=request_id) else: return render_template('common/error.html', message=MSG_INTERNAL_ERROR) else: # show password registration page if not session.get(SESSION_STUDENT_TO_REGISTER, None): return render_template('common/error.html', message=MSG_VIEW_SCHEDULE_FIRST) return render_template("user/passwordRegistration.html", name=session[SESSION_STUDENT_TO_REGISTER].name)
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)
def main(): """用户主页""" try: student = APIServer.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))
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)
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 = APIServer.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))
def get_teacher(url_tid, url_semester): """老师查询""" from collections import defaultdict from everyclass.server.utils import lesson_string_to_tuple from everyclass.server.utils import semester_calculate 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 # decrypt identifier in URL try: _, teacher_id = decrypt(url_tid, resource_type='teacher') except ValueError: return render_template("common/error.html", message=MSG_INVALID_IDENTIFIER) # RPC to get teacher timetable with elasticapm.capture_span('rpc_get_teacher_timetable'): try: teacher = APIServer.get_teacher_timetable(teacher_id, url_semester) except Exception as e: return handle_exception_with_error_page(e) with elasticapm.capture_span('process_rpc_result'): cards = defaultdict(list) for card in teacher.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, teacher.semesters) return render_template('query/teacher.html', teacher=teacher, 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)
def android_client_get_semester(identifier): """android client get a student or teacher's semesters """ from flask import jsonify from everyclass.server.rpc.api_server import APIServer try: search_result = APIServer.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
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)
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'))
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")) User.add_user(sid_orig=sid_orig, password=request.form['password']) 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 = 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_EMAIL_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_EMAIL_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')
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(): flash(MSG_INVALID_CAPTCHA) return redirect(url_for("user.login")) if request.form.get("xh", None): # 已手动填写用户名 student_id = request.form["xh"] # 检查学号是否存在 try: _ = APIServer.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 = APIServer.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"))
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)