diff --git a/doc/dev_doc/extensions_doc/plugins.rst b/doc/dev_doc/extensions_doc/plugins.rst index 125d0ae4ef..cd72a32c93 100644 --- a/doc/dev_doc/extensions_doc/plugins.rst +++ b/doc/dev_doc/extensions_doc/plugins.rst @@ -27,7 +27,7 @@ The following code adds a new page displaying ``This is a simple demo plugin`` o def init(plugin_manager, taskset_factory, client, plugin_config): """ Init the plugin """ - plugin_manager.add_page("/plugindemo", DemoPage.as_view('demopage')) + plugin_manager.add_page("/plugindemo", DemoPage.as_view('demopage')) The plugin is initialized by the plugin manager, which is the frontend-extended hook manager, by calling method ``init``. diff --git a/inginious/frontend/app.py b/inginious/frontend/app.py index 0e9140c13d..fd565f7885 100644 --- a/inginious/frontend/app.py +++ b/inginious/frontend/app.py @@ -100,20 +100,24 @@ def _put_configuration_defaults(config): return config +def get_homepath(): + """ Returns the URL root. """ + return flask.request.url_root[:-1] -def get_homepath(ignore_session=False, force_cookieless=False): +def get_path(*path_parts, force_cookieless=False): """ - :param ignore_session: Ignore the cookieless session_id that should be put in the URL + :param path_parts: List of elements in the path to be separated by slashes :param force_cookieless: Force the cookieless session; the link will include the session_creator if needed. """ session = flask.session request = flask.request - if not ignore_session and session.sid is not None and session.cookieless: - return request.url_root[:-1] + "/@" + session.sid + "@" - elif not ignore_session and force_cookieless: - return request.url_root[:-1] + "/@@" - else: - return request.url_root[:-1] + query_delimiter = '&' if path_parts and '?' in path_parts[-1] else '?' + path_parts = (get_homepath(), ) + path_parts + if session.sid is not None and session.cookieless: + return "/".join(path_parts) + f"{query_delimiter}session_id={session.sid}" + if force_cookieless: + return "/".join(path_parts) + f"{query_delimiter}session_id=" + return "/".join(path_parts) def _close_app(mongo_client, client): @@ -188,6 +192,7 @@ def get_app(config): if config.get("maintenance", False): template_helper = TemplateHelper(PluginManager(), None, config.get('use_minified_js', True)) template_helper.add_to_template_globals("get_homepath", get_homepath) + template_helper.add_to_template_globals("get_path", get_path) template_helper.add_to_template_globals("pkg_version", __version__) template_helper.add_to_template_globals("available_languages", available_languages) template_helper.add_to_template_globals("_", _) @@ -244,6 +249,7 @@ def get_app(config): template_helper.add_to_template_globals("str", str) template_helper.add_to_template_globals("available_languages", available_languages) template_helper.add_to_template_globals("get_homepath", get_homepath) + template_helper.add_to_template_globals("get_path", get_path) template_helper.add_to_template_globals("pkg_version", __version__) template_helper.add_to_template_globals("allow_registration", config.get("allow_registration", True)) template_helper.add_to_template_globals("sentry_io_url", config.get("sentry_io_url")) @@ -283,7 +289,7 @@ def flask_internalerror(e): flask_app.register_error_handler(InternalServerError, flask_internalerror) # Insert the needed singletons into the application, to allow pages to call them - flask_app.get_homepath = get_homepath + flask_app.get_path = get_path flask_app.plugin_manager = plugin_manager flask_app.taskset_factory = taskset_factory flask_app.course_factory = course_factory diff --git a/inginious/frontend/flask/mapping.py b/inginious/frontend/flask/mapping.py index 4ae54129a3..dc66c6706c 100644 --- a/inginious/frontend/flask/mapping.py +++ b/inginious/frontend/flask/mapping.py @@ -55,118 +55,105 @@ from inginious.frontend.pages.search_user import SearchUserPage -class CookielessConverter(BaseConverter): - # Parse the cookieless sessionid at the beginning of the url - regex = "@[a-f0-9A-F_]*@/|" - part_isolating = False - - def to_python(self, value): - return value[1:-2] - - def to_url(self, value): - return "@" + str(value) + "@/" - - def init_flask_maintenance_mapping(flask_app): flask_app.add_url_rule('/', view_func=MaintenancePage.as_view('maintenancepage.alias')) flask_app.add_url_rule('/', view_func=MaintenancePage.as_view('maintenancepage')) def init_flask_mapping(flask_app): - flask_app.url_map.converters['cookieless'] = CookielessConverter - flask_app.add_url_rule('/', view_func=IndexPage.as_view('indexpage')) - flask_app.add_url_rule('/index', view_func=IndexPage.as_view('indexpage.alias')) - flask_app.add_url_rule('/signin', view_func=SignInPage.as_view('signinpage')) - flask_app.add_url_rule('/logout', view_func=LogOutPage.as_view('logoutpage')) - flask_app.add_url_rule('/register', view_func=RegistrationPage.as_view('registrationpage')) - flask_app.add_url_rule('/queue', view_func=QueuePage.as_view('queuepage')) - flask_app.add_url_rule('/register/', + flask_app.add_url_rule('/', view_func=IndexPage.as_view('indexpage')) + flask_app.add_url_rule('/index', view_func=IndexPage.as_view('indexpage.alias')) + flask_app.add_url_rule('/signin', view_func=SignInPage.as_view('signinpage')) + flask_app.add_url_rule('/logout', view_func=LogOutPage.as_view('logoutpage')) + flask_app.add_url_rule('/register', view_func=RegistrationPage.as_view('registrationpage')) + flask_app.add_url_rule('/queue', view_func=QueuePage.as_view('queuepage')) + flask_app.add_url_rule('/register/', view_func=CourseRegisterPage.as_view('courseregisterpage')) - flask_app.add_url_rule('/marketplace', view_func=MarketplacePage.as_view('marketplacepage')) - flask_app.add_url_rule('/marketplace/', + flask_app.add_url_rule('/marketplace', view_func=MarketplacePage.as_view('marketplacepage')) + flask_app.add_url_rule('/marketplace/', view_func=MarketplaceTasksetPage.as_view('marketplacetasksetpage')) - flask_app.add_url_rule('/course/', view_func=CoursePage.as_view('coursepage')) - flask_app.add_url_rule('/course//', view_func=TaskPage.as_view('taskpage')) - flask_app.add_url_rule('/course///', + flask_app.add_url_rule('/course/', view_func=CoursePage.as_view('coursepage')) + flask_app.add_url_rule('/course//', view_func=TaskPage.as_view('taskpage')) + flask_app.add_url_rule('/course///', view_func=TaskPageStaticDownload.as_view('taskpagestaticdownload')) - flask_app.add_url_rule('/group/', view_func=GroupPage.as_view('grouppage')) - flask_app.add_url_rule('/user_settings/', view_func=CourseUserSettingPage.as_view('courseusersettingpage')) - flask_app.add_url_rule('/auth/signin/', + flask_app.add_url_rule('/group/', view_func=GroupPage.as_view('grouppage')) + flask_app.add_url_rule('/user_settings/', view_func=CourseUserSettingPage.as_view('courseusersettingpage')) + flask_app.add_url_rule('/auth/signin/', view_func=AuthenticationPage.as_view('authenticationpage')) - flask_app.add_url_rule('/auth/callback/', + flask_app.add_url_rule('/auth/callback/', view_func=CallbackPage.as_view('callbackpage')) - flask_app.add_url_rule('/pages/', view_func=INGIniousStaticPage.as_view('staticpage')) - flask_app.add_url_rule('/courselist', view_func=CourseListPage.as_view('courselistpage')) - flask_app.add_url_rule('/mycourses', view_func=MyCoursesPage.as_view('mycoursespage')) - flask_app.add_url_rule('/tasksets', view_func=TasksetsPage.as_view('tasksetspage')) - flask_app.add_url_rule('/preferences', view_func=PrefRedirectPage.as_view('prefredirectpage')) - flask_app.add_url_rule('/preferences/bindings', + flask_app.add_url_rule('/pages/', view_func=INGIniousStaticPage.as_view('staticpage')) + flask_app.add_url_rule('/courselist', view_func=CourseListPage.as_view('courselistpage')) + flask_app.add_url_rule('/mycourses', view_func=MyCoursesPage.as_view('mycoursespage')) + flask_app.add_url_rule('/tasksets', view_func=TasksetsPage.as_view('tasksetspage')) + flask_app.add_url_rule('/preferences', view_func=PrefRedirectPage.as_view('prefredirectpage')) + flask_app.add_url_rule('/preferences/bindings', view_func=BindingsPage.as_view('bindingspage')) - flask_app.add_url_rule('/preferences/delete', view_func=DeletePage.as_view('deletepage')) - flask_app.add_url_rule('/preferences/profile', view_func=ProfilePage.as_view('profilepage')) - flask_app.add_url_rule('/lti/task', view_func=LTITaskPage.as_view('ltitaskpage')) - flask_app.add_url_rule('/lti//', + flask_app.add_url_rule('/preferences/delete', view_func=DeletePage.as_view('deletepage')) + flask_app.add_url_rule('/preferences/profile', view_func=ProfilePage.as_view('profilepage')) + flask_app.add_url_rule('/lti/task', view_func=LTITaskPage.as_view('ltitaskpage')) + flask_app.add_url_rule('/lti//', view_func=LTILaunchPage.as_view('ltilaunchpage')) - flask_app.add_url_rule('/lti/bind', view_func=LTIBindPage.as_view('ltibindpage')) - flask_app.add_url_rule('/lti/login', view_func=LTILoginPage.as_view('ltiloginpage')) - flask_app.add_url_rule('/lti/asset/', + flask_app.add_url_rule('/lti/bind', view_func=LTIBindPage.as_view('ltibindpage')) + flask_app.add_url_rule('/lti/login', view_func=LTILoginPage.as_view('ltiloginpage')) + flask_app.add_url_rule('/lti/asset/', view_func=LTIAssetPage.as_view('ltiassetpage')) - flask_app.add_url_rule('/admin/', + flask_app.add_url_rule('/admin/', view_func=CourseRedirectPage.as_view('courseredirect')) - flask_app.add_url_rule('/admin//settings', + flask_app.add_url_rule('/admin//settings', view_func=CourseSettingsPage.as_view('coursesettingspage')) - flask_app.add_url_rule('/admin//students', + flask_app.add_url_rule('/admin//students', view_func=CourseStudentListPage.as_view('coursestudentlistpage')) - flask_app.add_url_rule('/admin//student/', + flask_app.add_url_rule('/admin//student/', view_func=CourseStudentInfoPage.as_view('coursestudentinfopage')) - flask_app.add_url_rule('/submission/', + flask_app.add_url_rule('/submission/', view_func=SubmissionPage.as_view('submissionpage')) - flask_app.add_url_rule('/admin//submissions', + flask_app.add_url_rule('/admin//submissions', view_func=CourseSubmissionsPage.as_view('coursesubmissionspage')) - flask_app.add_url_rule('/admin//tasks', + flask_app.add_url_rule('/admin//tasks', view_func=CourseTaskListPage.as_view('coursetasklistpage')) - flask_app.add_url_rule('/admin//edit/audience/', + flask_app.add_url_rule('/admin//edit/audience/', view_func=CourseEditAudience.as_view('courseditaudience')) - flask_app.add_url_rule('/taskset/', + flask_app.add_url_rule('/taskset/', view_func=TasksetRedirectPage.as_view('tasksetredirectpage')) - flask_app.add_url_rule('/taskset//settings', + flask_app.add_url_rule('/taskset//settings', view_func=TasksetSettingsPage.as_view('tasksetsettingspage')) - flask_app.add_url_rule('/taskset//edit/', + flask_app.add_url_rule('/taskset//edit/', view_func=EditTaskPage.as_view('tasksetedittask')) - flask_app.add_url_rule('/taskset//edit//files', + flask_app.add_url_rule('/taskset//edit//files', view_func=CourseTaskFiles.as_view('tasksettaskfiles')) - flask_app.add_url_rule('/taskset//edit//dd_upload', + flask_app.add_url_rule('/taskset//edit//dd_upload', view_func=CourseTaskFileUpload.as_view('tasksettaskfileupload')) - flask_app.add_url_rule('/taskset//template', + flask_app.add_url_rule('/taskset//template', view_func=TasksetTemplatePage.as_view('tasksettemplatepage')) - flask_app.add_url_rule('/taskset//danger', + flask_app.add_url_rule('/taskset//danger', view_func=TasksetDangerZonePage.as_view('tasksetdangerzonepage')) - flask_app.add_url_rule('/search_user/', + flask_app.add_url_rule('/search_user/', view_func=SearchUserPage.as_view('searchuserpage')) - flask_app.add_url_rule('/admin//danger', + flask_app.add_url_rule('/admin//danger', view_func=CourseDangerZonePage.as_view('coursedangerzonepage')) - flask_app.add_url_rule('/admin//stats', + flask_app.add_url_rule('/admin//stats', view_func=CourseStatisticsPage.as_view('coursestatisticspage')) - flask_app.add_url_rule('/api/v0/auth_methods', + flask_app.add_url_rule('/api/v0/auth_methods', view_func=APIAuthMethods.as_view('apiauthmethods')) - flask_app.add_url_rule('/api/v0/authentication', + flask_app.add_url_rule('/api/v0/authentication', view_func=APIAuthentication.as_view('apiauthentication')) - flask_app.add_url_rule('/api/v0/courses', view_func=APICourses.as_view('apicourses.alias'), + flask_app.add_url_rule('/api/v0/courses', view_func=APICourses.as_view('apicourses.alias'), defaults={'courseid': None}) - flask_app.add_url_rule('/api/v0/courses/', + flask_app.add_url_rule('/api/v0/courses/', view_func=APICourses.as_view('apicourses')) - flask_app.add_url_rule('/api/v0/courses//tasks', + flask_app.add_url_rule('/api/v0/courses//tasks', view_func=APITasks.as_view('apitasks.alias'), defaults={'taskid': None}) - flask_app.add_url_rule('/api/v0/courses//tasks/', + flask_app.add_url_rule('/api/v0/courses//tasks/', view_func=APITasks.as_view('apitasks')) - flask_app.add_url_rule('/api/v0/courses//tasks//submissions', + flask_app.add_url_rule('/api/v0/courses//tasks//submissions', view_func=APISubmissions.as_view('apisubmissions.alias')) - flask_app.add_url_rule('/api/v0/courses//tasks//submissions/', + flask_app.add_url_rule('/api/v0/courses//tasks//submissions/', view_func=APISubmissionSingle.as_view('apisubmissions')) - flask_app.add_url_rule('/administrator/users', + flask_app.add_url_rule('/administrator/users', view_func=AdministrationUsersPage.as_view('administrationuserspage')) - flask_app.add_url_rule('/administrator/user_action', + flask_app.add_url_rule('/administrator/user_action', view_func=AdministrationUserActionPage.as_view('administrationuseractionpage')) diff --git a/inginious/frontend/flask/mongo_sessions.py b/inginious/frontend/flask/mongo_sessions.py index 34d85999b7..77c8c85cfc 100644 --- a/inginious/frontend/flask/mongo_sessions.py +++ b/inginious/frontend/flask/mongo_sessions.py @@ -68,7 +68,7 @@ def _get_signer(self, app): def open_session(self, app, request): # Check for cookieless session in the path - path_session = re.match(r"(/@)([a-f0-9A-F_]*)(@)", request.path) + path_session = request.args.get('session_id') # Check if currently accessed URL is LTI launch page try: @@ -80,7 +80,7 @@ def open_session(self, app, request): if path_session: # Cookieless session cookieless = True - sid = path_session.group(2) + sid = path_session elif is_lti_launch: cookieless = True sid = None diff --git a/inginious/frontend/pages/course.py b/inginious/frontend/pages/course.py index 9cf97cf2bd..0ef103c7ae 100644 --- a/inginious/frontend/pages/course.py +++ b/inginious/frontend/pages/course.py @@ -11,14 +11,14 @@ from inginious.frontend.pages.utils import INGIniousAuthPage -def handle_course_unavailable(app_homepath, template_helper, user_manager, course): +def handle_course_unavailable(get_path, template_helper, user_manager, course): """ Displays the course_unavailable page or the course registration page """ reason = user_manager.course_is_open_to_user(course, lti=False, return_reason=True) if reason == "unregistered_not_previewable": username = user_manager.session_username() user_info = user_manager.get_user_info(username) if course.is_registration_possible(user_info): - return redirect(app_homepath + "/register/" + course.get_id()) + return redirect(get_path("register", course.get_id())) return template_helper.render("course_unavailable.html", reason=reason) @@ -45,7 +45,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ user_input = flask.request.form if "unregister" in user_input and course.allow_unregister(): self.user_manager.course_unregister_user(courseid, self.user_manager.session_username()) - return redirect(self.app.get_homepath() + '/mycourses') + return redirect(self.app.get_path('mycourses')) return self.show_page(course) @@ -58,7 +58,7 @@ def show_page(self, course): """ Prepares and shows the course page """ username = self.user_manager.session_username() if not self.user_manager.course_is_open_to_user(course, lti=False): - return handle_course_unavailable(self.app.get_homepath(), self.template_helper, self.user_manager, course) + return handle_course_unavailable(self.app.get_path, self.template_helper, self.user_manager, course) else: tasks = course.get_tasks() diff --git a/inginious/frontend/pages/course_admin/audience_edit.py b/inginious/frontend/pages/course_admin/audience_edit.py index 7f419cfbae..b80959d69e 100644 --- a/inginious/frontend/pages/course_admin/audience_edit.py +++ b/inginious/frontend/pages/course_admin/audience_edit.py @@ -88,7 +88,7 @@ def POST_AUTH(self, courseid, audienceid=''): # pylint: disable=arguments-diffe msg = _("Audience updated.") if audienceid and audienceid in data["delete"]: - return redirect(self.app.get_homepath() + "/admin/" + courseid + "/students?audiences") + return redirect(self.app.get_path("admin", courseid, "students?audiences")) else: audiences_dict = json.loads(data["audiences"]) student_list = self.user_manager.get_course_registered_users(course, False) diff --git a/inginious/frontend/pages/course_admin/danger_zone.py b/inginious/frontend/pages/course_admin/danger_zone.py index 51b63dcb64..33e78c1cfb 100644 --- a/inginious/frontend/pages/course_admin/danger_zone.py +++ b/inginious/frontend/pages/course_admin/danger_zone.py @@ -193,7 +193,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ else: try: self.delete_course(courseid) - return redirect(self.app.get_homepath() + '/index') + return redirect(self.app.get_path("index")) except Exception as ex: msg = _("An error occurred while deleting the course data: {}").format(repr(ex)) error = True diff --git a/inginious/frontend/pages/course_admin/submission.py b/inginious/frontend/pages/course_admin/submission.py index 9fe0a4b0d7..48d3118974 100644 --- a/inginious/frontend/pages/course_admin/submission.py +++ b/inginious/frontend/pages/course_admin/submission.py @@ -45,10 +45,10 @@ def POST_AUTH(self, submissionid): # pylint: disable=arguments-differ self.submission_manager.replay_job(course, task, submission, course.get_task_dispenser()) elif "replay-copy" in webinput: # Authorized for tutors self.submission_manager.replay_job(course, task, submission, course.get_task_dispenser(), True) - return redirect(self.app.get_homepath() + "/course/" + course.get_id() + "/" + task.get_id()) + return redirect(self.app.get_path("course", course.get_id(), task.get_id)) elif "replay-debug" in webinput and is_admin: self.submission_manager.replay_job(course, task, submission, course.get_task_dispenser(), True, "ssh") - return redirect(self.app.get_homepath() + "/course/" + course.get_id() + "/" + task.get_id()) + return redirect(self.app.get_path("course", course.get_id(), task.get_id)) return self.page(course, task, submission) diff --git a/inginious/frontend/pages/course_admin/utils.py b/inginious/frontend/pages/course_admin/utils.py index b8675400d7..36b4f3c54b 100644 --- a/inginious/frontend/pages/course_admin/utils.py +++ b/inginious/frontend/pages/course_admin/utils.py @@ -361,7 +361,7 @@ class CourseRedirectPage(INGIniousAdminPage): def GET_AUTH(self, courseid): # pylint: disable=arguments-differ """ GET request """ course, __ = self.get_course_and_check_rights(courseid) - return redirect(self.app.get_homepath() + '/admin/{}/settings'.format(courseid)) + return redirect(self.app.get_path("admin", courseid, "settings")) def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ diff --git a/inginious/frontend/pages/course_register.py b/inginious/frontend/pages/course_register.py index 04d759f77b..e187ceb83f 100644 --- a/inginious/frontend/pages/course_register.py +++ b/inginious/frontend/pages/course_register.py @@ -27,7 +27,7 @@ def basic_checks(self, courseid): user_info = self.user_manager.get_user_info(username) if self.user_manager.course_is_user_registered(course, username) or not course.is_registration_possible(user_info): - return redirect(self.app.get_homepath() + "/course/" + course.get_id()) + return redirect(self.app.get_path("course", course.get_id())) return course, username @@ -41,6 +41,6 @@ def POST_AUTH(self, courseid): success = self.user_manager.course_register_user(course, username, user_input.get("register_password", None)) if success: - return redirect(self.app.get_homepath() + "/course/" + course.get_id()) + return redirect(self.app.get_path("course", course.get_id())) else: return self.template_helper.render("course_register.html", course=course, error=True) diff --git a/inginious/frontend/pages/lti.py b/inginious/frontend/pages/lti.py index 02d953ab65..6be4f6f4be 100644 --- a/inginious/frontend/pages/lti.py +++ b/inginious/frontend/pages/lti.py @@ -45,48 +45,48 @@ def GET_AUTH(self, asset_url): if data is None: raise Forbidden(description=_("No LTI data available.")) (courseid, _) = data['task'] - return redirect(self.app.get_homepath() + "/course/{courseid}/{asset_url}".format(courseid=courseid, asset_url=asset_url)) + return redirect(self.app.get_path("course", courseid, asset_url)) class LTIBindPage(INGIniousAuthPage): def is_lti_page(self): return False - def fetch_lti_data(self, sessionid): + def fetch_lti_data(self, session_id): # TODO : Flask session interface does not allow to open a specific session # It could be worth putting these information outside of the session dict - sess = self.database.sessions.find_one({"_id": sessionid}) + sess = self.database.sessions.find_one({"_id": session_id}) if sess: cookieless_session = self.app.session_interface.serializer.loads(want_bytes(sess['data'])) else: return KeyError() - return sessionid, cookieless_session["lti"] + return session_id, cookieless_session["lti"] def GET_AUTH(self): input_data = flask.request.args - if "sessionid" not in input_data: - return self.template_helper.render("lti_bind.html", success=False, sessionid="", + if "session_id" not in input_data: + return self.template_helper.render("lti_bind.html", success=False, session_id="", data=None, error=_("Missing LTI session id")) try: - cookieless_session_id, data = self.fetch_lti_data(input_data["sessionid"]) + cookieless_session_id, data = self.fetch_lti_data(input_data["session_id"]) except KeyError: - return self.template_helper.render("lti_bind.html", success=False, sessionid="", + return self.template_helper.render("lti_bind.html", success=False, session_id="", data=None, error=_("Invalid LTI session id")) return self.template_helper.render("lti_bind.html", success=False, - sessionid=cookieless_session_id, data=data, error="") + session_id=cookieless_session_id, data=data, error="") def POST_AUTH(self): input_data = flask.request.args - if "sessionid" not in input_data: - return self.template_helper.render("lti_bind.html",success=False, sessionid="", + if "session_id" not in input_data: + return self.template_helper.render("lti_bind.html",success=False, session_id="", data= None, error=_("Missing LTI session id")) try: - cookieless_session_id, data = self.fetch_lti_data(input_data["sessionid"]) + cookieless_session_id, data = self.fetch_lti_data(input_data["session_id"]) except KeyError: - return self.template_helper.render("lti_bind.html", success=False, sessionid="", + return self.template_helper.render("lti_bind.html", success=False, session_id="", data=None, error=_("Invalid LTI session id")) try: @@ -94,7 +94,7 @@ def POST_AUTH(self): if data["consumer_key"] not in course.lti_keys().keys(): raise Exception() except: - return self.template_helper.render("lti_bind.html", success=False, sessionid="", + return self.template_helper.render("lti_bind.html", success=False, session_id="", data=None, error=_("Invalid LTI data")) if data: @@ -115,12 +115,12 @@ def POST_AUTH(self): data["consumer_key"], user_profile.get("ltibindings", {}).get(data["task"][0], {}).get(data["consumer_key"], "")) return self.template_helper.render("lti_bind.html", success=False, - sessionid=cookieless_session_id, + session_id=cookieless_session_id, data=data, error=_("Your account is already bound with this context.")) return self.template_helper.render("lti_bind.html", success=True, - sessionid=cookieless_session_id, data=data, error="") + session_id=cookieless_session_id, data=data, error="") class LTILoginPage(INGIniousPage): @@ -141,7 +141,7 @@ def GET(self): if data["consumer_key"] not in course.lti_keys().keys(): raise Exception() except: - return self.template_helper.render("lti_bind.html", success=False, sessionid="", + return self.template_helper.render("lti_bind.html", success=False, session_id="", data=None, error="Invalid LTI data") user_profile = self.database.users.find_one({"ltibindings." + data["task"][0] + "." + data["consumer_key"]: data["username"]}) @@ -150,7 +150,7 @@ def GET(self): user_profile["language"], user_profile.get("tos_accepted", False)) if self.user_manager.session_logged_in(): - return redirect(self.app.get_homepath() + "/lti/task") + return redirect(self.app.get_path("lti", "task")) return self.template_helper.render("lti_login.html") @@ -172,11 +172,11 @@ def GET(self, courseid, taskid): raise MethodNotAllowed() def POST(self, courseid, taskid): - (sessionid, loggedin) = self._parse_lti_data(courseid, taskid) + (session_id, loggedin) = self._parse_lti_data(courseid, taskid) if loggedin: - return redirect(self.app.get_homepath() + "/lti/task") + return redirect(self.app.get_path("lti", "task")) else: - return redirect(self.app.get_homepath() + "/lti/login") + return redirect(self.app.get_path("lti", "login")) def _parse_lti_data(self, courseid, taskid): """ Verify and parse the data for the LTI basic launch """ diff --git a/inginious/frontend/pages/marketplace.py b/inginious/frontend/pages/marketplace.py index a92640a802..5b8df12e53 100644 --- a/inginious/frontend/pages/marketplace.py +++ b/inginious/frontend/pages/marketplace.py @@ -50,7 +50,7 @@ def POST_AUTH(self): # pylint: disable=arguments-differ except: errors.append(_("User returned an invalid form.")) if not errors: - return redirect(self.app.get_homepath() + "/taskset/{}".format(new_tasksetid)) + return redirect(self.app.get_path("taskset", new_tasksetid)) return self.show_page(errors) def show_page(self, errors=None): diff --git a/inginious/frontend/pages/marketplace_taskset.py b/inginious/frontend/pages/marketplace_taskset.py index a8904c83ce..5de45588e5 100644 --- a/inginious/frontend/pages/marketplace_taskset.py +++ b/inginious/frontend/pages/marketplace_taskset.py @@ -51,7 +51,7 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ except ImportTasksetException as e: errors.append(str(e)) if not errors: - return redirect(self.app.get_homepath() + "/taskset/{}".format(new_tasksetid)) + return redirect(self.app.get_path("taskset", new_tasksetid)) return self.show_page(taskset, errors) def show_page(self, taskset, errors=None): diff --git a/inginious/frontend/pages/tasks.py b/inginious/frontend/pages/tasks.py index 6515465c7c..f9e4c60662 100644 --- a/inginious/frontend/pages/tasks.py +++ b/inginious/frontend/pages/tasks.py @@ -61,7 +61,7 @@ def GET(self, courseid, taskid, is_LTI): self.user_manager.course_register_user(course, force=True) if not self.user_manager.course_is_open_to_user(course, username, is_LTI): - return handle_course_unavailable(self.cp.app.get_homepath(), self.template_helper, self.user_manager, course) + return handle_course_unavailable(self.cp.app.get_path, self.template_helper, self.user_manager, course) is_staff = self.user_manager.has_admin_rights_on_course(course, username) @@ -161,7 +161,7 @@ def POST(self, courseid, taskid, isLTI): course = self.course_factory.get_course(courseid) if not self.user_manager.course_is_open_to_user(course, username, isLTI): - return handle_course_unavailable(self.cp.app.get_homepath(), self.template_helper, self.user_manager, course) + return handle_course_unavailable(self.cp.app.get_path, self.template_helper, self.user_manager, course) is_admin = self.user_manager.has_admin_rights_on_course(course, username) @@ -399,7 +399,7 @@ def GET(self, courseid, taskid, path): # pylint: disable=arguments-differ try: course = self.course_factory.get_course(courseid) if not self.user_manager.course_is_open_to_user(course): - return handle_course_unavailable(self.app.get_homepath(), self.template_helper, self.user_manager, course) + return handle_course_unavailable(self.cp.app.get_path, self.template_helper, self.user_manager, course) path_norm = posixpath.normpath(urllib.parse.unquote(path)) diff --git a/inginious/frontend/pages/taskset_admin/danger_zone.py b/inginious/frontend/pages/taskset_admin/danger_zone.py index c189991817..e263f5738c 100644 --- a/inginious/frontend/pages/taskset_admin/danger_zone.py +++ b/inginious/frontend/pages/taskset_admin/danger_zone.py @@ -58,7 +58,7 @@ def POST_AUTH(self, tasksetid): # pylint: disable=arguments-differ if self.database.courses.find_one({"tasksetid": tasksetid}): raise Exception(_("One or more course(s) rely on the current taskset.")) self.delete_taskset(tasksetid) - return redirect(self.app.get_homepath() + '/tasksets') + return redirect(self.app.get_path("tasksets")) except Exception as ex: msg = _("An error occurred while deleting the taskset data: {}").format(str(ex)) error = True diff --git a/inginious/frontend/pages/taskset_admin/utils.py b/inginious/frontend/pages/taskset_admin/utils.py index 7b2938d0c9..bd85213f9e 100644 --- a/inginious/frontend/pages/taskset_admin/utils.py +++ b/inginious/frontend/pages/taskset_admin/utils.py @@ -49,7 +49,7 @@ class TasksetRedirectPage(INGIniousAdminPage): def GET_AUTH(self, tasksetid): # pylint: disable=arguments-differ """ GET request """ taskset, __ = self.get_taskset_and_check_rights(tasksetid) - return redirect(self.app.get_homepath() + '/taskset/{}/settings'.format(tasksetid)) + return redirect(self.app.get_path("taskset", tasksetid, "settings")) def POST_AUTH(self, courseid): # pylint: disable=arguments-differ """ POST request """ diff --git a/inginious/frontend/pages/utils.py b/inginious/frontend/pages/utils.py index 40684b3e54..06fd15720f 100644 --- a/inginious/frontend/pages/utils.py +++ b/inginious/frontend/pages/utils.py @@ -12,7 +12,7 @@ from gridfs import GridFS from flask import redirect, url_for from flask.views import MethodView -from werkzeug.exceptions import NotFound, NotAcceptable +from werkzeug.exceptions import NotFound, NotAcceptable, MethodNotAllowed from inginious.client.client import Client from inginious.common import custom_yaml @@ -46,8 +46,8 @@ def app(self): """ Returns the web application singleton """ return flask.current_app - def _pre_check(self, sessionid): - # Check for language + def _pre_check(self): + """ Checks for language. """ if "lang" in flask.request.args and flask.request.args["lang"] in self.app.l10n_manager.translations.keys(): self.user_manager.set_session_language(flask.request.args["lang"]) elif "language" not in flask.session: @@ -55,15 +55,23 @@ def _pre_check(self, sessionid): default="en") self.user_manager.set_session_language(best_lang) - return "" - - def get(self, sessionid, *args, **kwargs): - pre_check = self._pre_check(sessionid) - return pre_check if pre_check else self.GET(*args, **kwargs) + def GET(self, *args, **kwargs): + """ Handles GET requests. It should be redefined by subclasses. """ + raise MethodNotAllowed() - def post(self, sessionid, *args, **kwargs): - pre_check = self._pre_check(sessionid) - return pre_check if pre_check else self.POST(*args, **kwargs) + def POST(self, *args, **kwargs): + """ Handles POST requests. It should be redefined by subclasses. """ + raise MethodNotAllowed() + + def get(self, *args, **kwargs): + """ Interfaces INGInious pages with Flask views for GET requests. """ + self._pre_check() + return self.GET(*args, **kwargs) + + def post(self, *args, **kwargs): + """ Interfaces INGInious pages with Flask views for POST requests. """ + self._pre_check() + return self.POST(*args, **kwargs) @property def plugin_manager(self) -> PluginManager: diff --git a/inginious/frontend/parsable_text.py b/inginious/frontend/parsable_text.py index 8dceffeec1..f910f503b5 100644 --- a/inginious/frontend/parsable_text.py +++ b/inginious/frontend/parsable_text.py @@ -151,7 +151,7 @@ def starttag(self, node, tagname, suffix='\n', empty=False, **attributes): attributes["target"] = "_blank" # Rewrite paths if we are in LTI mode # TODO: this should be an argument passed through all the functions - if re.match(r"^(/@[a-f0-9A-F_]*@)", flask.request.path if flask.has_app_context() else ""): + if flask.request.args.get('session_id'): if tagname == 'a' and 'href' in attributes: attributes['href'] = self.rewrite_lti_url(attributes['href']) elif tagname == 'img' and 'src' in attributes: @@ -162,7 +162,7 @@ def starttag(self, node, tagname, suffix='\n', empty=False, **attributes): def rewrite_lti_url(url): if urlparse(url).netloc: # If URL is absolute, don't do anything return url - return 'asset/' + url + return 'asset/' + url + '?session_id=' + flask.request.args['session_id'] def visit_table(self, node): """ Remove needless borders """ diff --git a/inginious/frontend/plugin_manager.py b/inginious/frontend/plugin_manager.py index 09efb2720d..ca6413f1ea 100644 --- a/inginious/frontend/plugin_manager.py +++ b/inginious/frontend/plugin_manager.py @@ -93,7 +93,7 @@ def add_page(self, pattern, classname_or_viewfunc): if not self._loaded: raise PluginManagerNotLoadedException() - self._flask_app.add_url_rule("/" + pattern[1:], view_func=classname_or_viewfunc) + self._flask_app.add_url_rule("/" + pattern[1:], view_func=classname_or_viewfunc) def add_task_file_manager(self, task_file_manager): """ Add a task file manager. Only available after that the Plugin Manager is loaded """ diff --git a/inginious/frontend/plugins/contests/admin.html b/inginious/frontend/plugins/contests/admin.html index 6328b802e5..162bec52ee 100644 --- a/inginious/frontend/plugins/contests/admin.html +++ b/inginious/frontend/plugins/contests/admin.html @@ -12,8 +12,8 @@ {% block navbar %}