def js_set_preference(): """AJAX更新偏好设置""" if request.form.get("privacyLevel", None): # update privacy level privacy_level = int(request.form["privacyLevel"]) if privacy_level not in (0, 1, 2): logger.warn("Received malformed set preference request. privacyLevel value not valid.") return jsonify({"acknowledged": False, "message" : "Invalid value"}) PrivacySettings.set_level(session[SESSION_CURRENT_USER].sid_orig, privacy_level) return jsonify({"acknowledged": True})
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))
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 check_permission( student: StudentTimetableResult) -> Tuple[bool, Optional[str]]: """ 检查当前登录的用户是否有权限访问此学生 :param student: 被访问的学生 :return: 第一个返回值为布尔类型,True 标识可以访问,False 表示没有权限访问。第二个返回值为没有权限访问时需要返回的模板 """ 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 False, 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 False, 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 False, render_template('query/studentBlocked.html', name=student.name, falculty=student.deputy, class_name=student.klass, level=3) # 公开或实名互访模式、已登录、不是自己访问自己,则留下轨迹 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]) return True, None
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 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): """学生查询""" 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)