From 29067a6b8b7762422120b4d60d67e0545e7838f5 Mon Sep 17 00:00:00 2001
From: Maxime Piraux
Date: Tue, 15 Oct 2024 15:56:21 +0200
Subject: [PATCH] Reworks the cookieless session id from path to parameters
---
doc/dev_doc/extensions_doc/plugins.rst | 2 +-
inginious/frontend/app.py | 24 ++--
inginious/frontend/flask/mapping.py | 127 ++++++++---------
inginious/frontend/flask/mongo_sessions.py | 4 +-
inginious/frontend/pages/course.py | 8 +-
.../pages/course_admin/audience_edit.py | 2 +-
.../pages/course_admin/danger_zone.py | 2 +-
.../frontend/pages/course_admin/submission.py | 4 +-
.../frontend/pages/course_admin/utils.py | 2 +-
inginious/frontend/pages/course_register.py | 4 +-
inginious/frontend/pages/lti.py | 42 +++---
inginious/frontend/pages/marketplace.py | 2 +-
.../frontend/pages/marketplace_taskset.py | 2 +-
inginious/frontend/pages/tasks.py | 6 +-
.../pages/taskset_admin/danger_zone.py | 2 +-
.../frontend/pages/taskset_admin/utils.py | 2 +-
inginious/frontend/pages/utils.py | 30 ++--
inginious/frontend/parsable_text.py | 4 +-
inginious/frontend/plugin_manager.py | 2 +-
.../frontend/plugins/contests/admin.html | 6 +-
.../plugins/contests/course_menu.html | 2 +-
.../frontend/plugins/contests/scoreboard.html | 4 +-
.../plugins/scoreboard/course_menu.html | 2 +-
.../frontend/plugins/scoreboard/main.html | 4 +-
.../plugins/scoreboard/scoreboard.html | 4 +-
.../plugins/scoreboard/task_menu.html | 2 +-
.../templates/coming_tasks.html | 4 +-
.../upcoming_tasks/templates/main_menu.html | 2 +-
.../templates/upcoming_task_list.html | 2 +-
inginious/frontend/templates/auth.html | 4 +-
inginious/frontend/templates/course.html | 16 +--
.../templates/course_admin/audience_edit.html | 8 +-
.../templates/course_admin/danger_zone.html | 6 +-
.../frontend/templates/course_admin/menu.html | 2 +-
.../templates/course_admin/settings.html | 8 +-
.../templates/course_admin/stats.html | 14 +-
.../templates/course_admin/student_info.html | 18 +--
.../templates/course_admin/student_list.html | 15 +-
.../course_admin/student_list_table.html | 6 +-
.../templates/course_admin/submission.html | 18 +--
.../templates/course_admin/submissions.html | 10 +-
.../course_admin/submissions_query.html | 3 +-
.../templates/course_admin/task_list.html | 6 +-
.../frontend/templates/course_register.html | 4 +-
.../templates/course_unavailable.html | 4 +-
.../templates/course_user_settings.html | 12 +-
inginious/frontend/templates/courselist.html | 2 +-
inginious/frontend/templates/forbidden.html | 2 +-
inginious/frontend/templates/group.html | 4 +-
.../frontend/templates/internalerror.html | 2 +-
inginious/frontend/templates/layout.html | 128 +++++++++---------
inginious/frontend/templates/lti_bind.html | 2 +-
inginious/frontend/templates/lti_login.html | 7 +-
inginious/frontend/templates/maintenance.html | 2 +-
inginious/frontend/templates/marketplace.html | 2 +-
inginious/frontend/templates/mycourses.html | 4 +-
inginious/frontend/templates/notfound.html | 2 +-
.../templates/preferences/bindings.html | 4 +-
.../templates/preferences/delete.html | 2 +-
.../frontend/templates/preferences/menu.html | 2 +-
.../templates/preferences/profile.html | 2 +-
.../frontend/templates/signin_button.html | 2 +-
inginious/frontend/templates/task.html | 24 ++--
.../templates/task_dispensers/task_list.html | 2 +-
.../task_dispensers_admin/task_buttons.html | 6 +-
.../frontend/templates/task_unavailable.html | 2 +-
.../templates/taskset_admin/danger_zone.html | 6 +-
.../taskset_admin/edit_tabs/files.html | 4 +-
.../templates/taskset_admin/menu.html | 2 +-
.../templates/taskset_admin/settings.html | 10 +-
.../templates/taskset_admin/task_edit.html | 6 +-
.../templates/taskset_admin/template.html | 6 +-
inginious/frontend/templates/tasksets.html | 2 +-
.../frontend/templates/unregister_modal.html | 2 +-
.../templates/user_selection_box.html | 3 +-
75 files changed, 353 insertions(+), 348 deletions(-)
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 %}
-