Skip to content

Commit

Permalink
Reworks the cookieless session id from path to parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
mpiraux committed Oct 15, 2024
1 parent fcb729e commit 3591e25
Show file tree
Hide file tree
Showing 75 changed files with 352 additions and 347 deletions.
2 changes: 1 addition & 1 deletion doc/dev_doc/extensions_doc/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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("/<cookieless:sessionid>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``.
Expand Down
24 changes: 15 additions & 9 deletions inginious/frontend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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("_", _)
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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
Expand Down
127 changes: 57 additions & 70 deletions inginious/frontend/flask/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('/<path:path>', view_func=MaintenancePage.as_view('maintenancepage'))


def init_flask_mapping(flask_app):
flask_app.url_map.converters['cookieless'] = CookielessConverter
flask_app.add_url_rule('/<cookieless:sessionid>', view_func=IndexPage.as_view('indexpage'))
flask_app.add_url_rule('/<cookieless:sessionid>index', view_func=IndexPage.as_view('indexpage.alias'))
flask_app.add_url_rule('/<cookieless:sessionid>signin', view_func=SignInPage.as_view('signinpage'))
flask_app.add_url_rule('/<cookieless:sessionid>logout', view_func=LogOutPage.as_view('logoutpage'))
flask_app.add_url_rule('/<cookieless:sessionid>register', view_func=RegistrationPage.as_view('registrationpage'))
flask_app.add_url_rule('/<cookieless:sessionid>queue', view_func=QueuePage.as_view('queuepage'))
flask_app.add_url_rule('/<cookieless:sessionid>register/<courseid>',
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/<courseid>',
view_func=CourseRegisterPage.as_view('courseregisterpage'))
flask_app.add_url_rule('/<cookieless:sessionid>marketplace', view_func=MarketplacePage.as_view('marketplacepage'))
flask_app.add_url_rule('/<cookieless:sessionid>marketplace/<tasksetid>',
flask_app.add_url_rule('/marketplace', view_func=MarketplacePage.as_view('marketplacepage'))
flask_app.add_url_rule('/marketplace/<tasksetid>',
view_func=MarketplaceTasksetPage.as_view('marketplacetasksetpage'))
flask_app.add_url_rule('/<cookieless:sessionid>course/<courseid>', view_func=CoursePage.as_view('coursepage'))
flask_app.add_url_rule('/<cookieless:sessionid>course/<courseid>/<taskid>', view_func=TaskPage.as_view('taskpage'))
flask_app.add_url_rule('/<cookieless:sessionid>course/<courseid>/<taskid>/<path:path>',
flask_app.add_url_rule('/course/<courseid>', view_func=CoursePage.as_view('coursepage'))
flask_app.add_url_rule('/course/<courseid>/<taskid>', view_func=TaskPage.as_view('taskpage'))
flask_app.add_url_rule('/course/<courseid>/<taskid>/<path:path>',
view_func=TaskPageStaticDownload.as_view('taskpagestaticdownload'))
flask_app.add_url_rule('/<cookieless:sessionid>group/<courseid>', view_func=GroupPage.as_view('grouppage'))
flask_app.add_url_rule('/<cookieless:sessionid>user_settings/<courseid>', view_func=CourseUserSettingPage.as_view('courseusersettingpage'))
flask_app.add_url_rule('/<cookieless:sessionid>auth/signin/<auth_id>',
flask_app.add_url_rule('/group/<courseid>', view_func=GroupPage.as_view('grouppage'))
flask_app.add_url_rule('/user_settings/<courseid>', view_func=CourseUserSettingPage.as_view('courseusersettingpage'))
flask_app.add_url_rule('/auth/signin/<auth_id>',
view_func=AuthenticationPage.as_view('authenticationpage'))
flask_app.add_url_rule('/<cookieless:sessionid>auth/callback/<auth_id>',
flask_app.add_url_rule('/auth/callback/<auth_id>',
view_func=CallbackPage.as_view('callbackpage'))
flask_app.add_url_rule('/<cookieless:sessionid>pages/<pageid>', view_func=INGIniousStaticPage.as_view('staticpage'))
flask_app.add_url_rule('/<cookieless:sessionid>courselist', view_func=CourseListPage.as_view('courselistpage'))
flask_app.add_url_rule('/<cookieless:sessionid>mycourses', view_func=MyCoursesPage.as_view('mycoursespage'))
flask_app.add_url_rule('/<cookieless:sessionid>tasksets', view_func=TasksetsPage.as_view('tasksetspage'))
flask_app.add_url_rule('/<cookieless:sessionid>preferences', view_func=PrefRedirectPage.as_view('prefredirectpage'))
flask_app.add_url_rule('/<cookieless:sessionid>preferences/bindings',
flask_app.add_url_rule('/pages/<pageid>', 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('/<cookieless:sessionid>preferences/delete', view_func=DeletePage.as_view('deletepage'))
flask_app.add_url_rule('/<cookieless:sessionid>preferences/profile', view_func=ProfilePage.as_view('profilepage'))
flask_app.add_url_rule('/<cookieless:sessionid>lti/task', view_func=LTITaskPage.as_view('ltitaskpage'))
flask_app.add_url_rule('/<cookieless:sessionid>lti/<courseid>/<taskid>',
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/<courseid>/<taskid>',
view_func=LTILaunchPage.as_view('ltilaunchpage'))
flask_app.add_url_rule('/<cookieless:sessionid>lti/bind', view_func=LTIBindPage.as_view('ltibindpage'))
flask_app.add_url_rule('/<cookieless:sessionid>lti/login', view_func=LTILoginPage.as_view('ltiloginpage'))
flask_app.add_url_rule('/<cookieless:sessionid>lti/asset/<path:asset_url>',
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/<path:asset_url>',
view_func=LTIAssetPage.as_view('ltiassetpage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>',
flask_app.add_url_rule('/admin/<courseid>',
view_func=CourseRedirectPage.as_view('courseredirect'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/settings',
flask_app.add_url_rule('/admin/<courseid>/settings',
view_func=CourseSettingsPage.as_view('coursesettingspage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/students',
flask_app.add_url_rule('/admin/<courseid>/students',
view_func=CourseStudentListPage.as_view('coursestudentlistpage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/student/<username>',
flask_app.add_url_rule('/admin/<courseid>/student/<username>',
view_func=CourseStudentInfoPage.as_view('coursestudentinfopage'))
flask_app.add_url_rule('/<cookieless:sessionid>submission/<submissionid>',
flask_app.add_url_rule('/submission/<submissionid>',
view_func=SubmissionPage.as_view('submissionpage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/submissions',
flask_app.add_url_rule('/admin/<courseid>/submissions',
view_func=CourseSubmissionsPage.as_view('coursesubmissionspage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/tasks',
flask_app.add_url_rule('/admin/<courseid>/tasks',
view_func=CourseTaskListPage.as_view('coursetasklistpage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/edit/audience/<audienceid>',
flask_app.add_url_rule('/admin/<courseid>/edit/audience/<audienceid>',
view_func=CourseEditAudience.as_view('courseditaudience'))

flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>',
flask_app.add_url_rule('/taskset/<tasksetid>',
view_func=TasksetRedirectPage.as_view('tasksetredirectpage'))
flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>/settings',
flask_app.add_url_rule('/taskset/<tasksetid>/settings',
view_func=TasksetSettingsPage.as_view('tasksetsettingspage'))
flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>/edit/<taskid>',
flask_app.add_url_rule('/taskset/<tasksetid>/edit/<taskid>',
view_func=EditTaskPage.as_view('tasksetedittask'))
flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>/edit/<taskid>/files',
flask_app.add_url_rule('/taskset/<tasksetid>/edit/<taskid>/files',
view_func=CourseTaskFiles.as_view('tasksettaskfiles'))
flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>/edit/<taskid>/dd_upload',
flask_app.add_url_rule('/taskset/<tasksetid>/edit/<taskid>/dd_upload',
view_func=CourseTaskFileUpload.as_view('tasksettaskfileupload'))
flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>/template',
flask_app.add_url_rule('/taskset/<tasksetid>/template',
view_func=TasksetTemplatePage.as_view('tasksettemplatepage'))
flask_app.add_url_rule('/<cookieless:sessionid>taskset/<tasksetid>/danger',
flask_app.add_url_rule('/taskset/<tasksetid>/danger',
view_func=TasksetDangerZonePage.as_view('tasksetdangerzonepage'))

flask_app.add_url_rule('/<cookieless:sessionid>search_user/<request>',
flask_app.add_url_rule('/search_user/<request>',
view_func=SearchUserPage.as_view('searchuserpage'))

flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/danger',
flask_app.add_url_rule('/admin/<courseid>/danger',
view_func=CourseDangerZonePage.as_view('coursedangerzonepage'))
flask_app.add_url_rule('/<cookieless:sessionid>admin/<courseid>/stats',
flask_app.add_url_rule('/admin/<courseid>/stats',
view_func=CourseStatisticsPage.as_view('coursestatisticspage'))
flask_app.add_url_rule('/<cookieless:sessionid>api/v0/auth_methods',
flask_app.add_url_rule('/api/v0/auth_methods',
view_func=APIAuthMethods.as_view('apiauthmethods'))
flask_app.add_url_rule('/<cookieless:sessionid>api/v0/authentication',
flask_app.add_url_rule('/api/v0/authentication',
view_func=APIAuthentication.as_view('apiauthentication'))
flask_app.add_url_rule('/<cookieless:sessionid>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('/<cookieless:sessionid>api/v0/courses/<courseid>',
flask_app.add_url_rule('/api/v0/courses/<courseid>',
view_func=APICourses.as_view('apicourses'))
flask_app.add_url_rule('/<cookieless:sessionid>api/v0/courses/<courseid>/tasks',
flask_app.add_url_rule('/api/v0/courses/<courseid>/tasks',
view_func=APITasks.as_view('apitasks.alias'), defaults={'taskid': None})
flask_app.add_url_rule('/<cookieless:sessionid>api/v0/courses/<courseid>/tasks/<taskid>',
flask_app.add_url_rule('/api/v0/courses/<courseid>/tasks/<taskid>',
view_func=APITasks.as_view('apitasks'))
flask_app.add_url_rule('/<cookieless:sessionid>api/v0/courses/<courseid>/tasks/<taskid>/submissions',
flask_app.add_url_rule('/api/v0/courses/<courseid>/tasks/<taskid>/submissions',
view_func=APISubmissions.as_view('apisubmissions.alias'))
flask_app.add_url_rule('/<cookieless:sessionid>api/v0/courses/<courseid>/tasks/<taskid>/submissions/<submissionid>',
flask_app.add_url_rule('/api/v0/courses/<courseid>/tasks/<taskid>/submissions/<submissionid>',
view_func=APISubmissionSingle.as_view('apisubmissions'))
flask_app.add_url_rule('/<cookieless:sessionid>administrator/users',
flask_app.add_url_rule('/administrator/users',
view_func=AdministrationUsersPage.as_view('administrationuserspage'))
flask_app.add_url_rule('/<cookieless:sessionid>administrator/user_action',
flask_app.add_url_rule('/administrator/user_action',
view_func=AdministrationUserActionPage.as_view('administrationuseractionpage'))
4 changes: 2 additions & 2 deletions inginious/frontend/flask/mongo_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions inginious/frontend/pages/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -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)

Expand All @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion inginious/frontend/pages/course_admin/audience_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 3591e25

Please sign in to comment.