Source code for inginious.frontend.pages.utils

# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.

""" Some utils for all the pages """
import logging
import os

from flask import current_app, redirect, render_template, session, request, url_for
from flask.views import MethodView
from werkzeug.exceptions import NotFound, NotAcceptable, MethodNotAllowed

from inginious.client.client import Client
from inginious.common import custom_yaml
from inginious.frontend.submission_manager import WebAppSubmissionManager
from inginious.frontend.user_manager import UserManager
from inginious.frontend.parsable_text import ParsableText
from inginious.frontend.i18n import available_languages


[docs] class INGIniousPage(MethodView): """ A base for all the pages of the INGInious webapp. Contains references to the PluginManager, the CourseFactory, and the SubmissionManager """ @property def is_lti_page(self): """ True if the current page allows LTI sessions. False else. """ return False def _pre_check(self): """ Checks for language. """ if "lang" in request.args and request.args["lang"] in available_languages: session.language = request.args["lang"] elif not session.language: best_lang = request.accept_languages.best_match(available_languages,default="en") session.language = best_lang
[docs] def GET(self, *args, **kwargs): """ Handles GET requests. It should be redefined by subclasses. """ raise MethodNotAllowed()
[docs] def POST(self, *args, **kwargs): """ Handles POST requests. It should be redefined by subclasses. """ raise MethodNotAllowed()
[docs] def get(self, *args, **kwargs): """ Interfaces INGInious pages with Flask views for GET requests. """ self._pre_check() return self.GET(*args, **kwargs)
[docs] def post(self, *args, **kwargs): """ Interfaces INGInious pages with Flask views for POST requests. """ self._pre_check() return self.POST(*args, **kwargs)
@property def submission_manager(self) -> WebAppSubmissionManager: """ Returns the submission manager singleton""" return current_app.submission_manager @property def user_manager(self) -> UserManager: """ Returns the user manager singleton """ return current_app.user_manager @property def client(self) -> Client: """ Returns the INGInious client """ return current_app.client @property def logger(self) -> logging.Logger: """ Logger """ return logging.getLogger('inginious.webapp.pages')
[docs] class INGIniousAuthPage(INGIniousPage): """ Augmented version of INGIniousPage that checks if user is authenticated. """
[docs] def POST_AUTH(self, *args, **kwargs): # pylint: disable=unused-argument raise NotAcceptable()
[docs] def GET_AUTH(self, *args, **kwargs): # pylint: disable=unused-argument raise NotAcceptable()
[docs] def GET(self, *args, **kwargs): """ Checks if user is authenticated and calls GET_AUTH or performs logout. Otherwise, returns the login template. """ if session.loggedin: if (not session.username or (current_app.config["IS_TOS_DEFINED"] and not session.tos_signed)) \ and not self.__class__.__name__ == "ProfilePage": return redirect(url_for("profilepage")) if not self.is_lti_page and session.is_lti: # lti session self.user_manager.disconnect_user() return render_template("auth.html", auth_methods=self.user_manager.get_auth_methods()) return self.GET_AUTH(*args, **kwargs) elif self.preview_allowed(*args, **kwargs): return self.GET_AUTH(*args, **kwargs) else: error = '' if "binderror" in request.args: error = _("An account using this email already exists and is not bound with this service. " "For security reasons, please log in via another method and bind your account in your profile.") if "callbackerror" in request.args: error = _("Couldn't fetch the required information from the service. Please check the provided " "permissions (name, email) and contact your INGInious administrator if the error persists.") return render_template("auth.html", auth_methods=self.user_manager.get_auth_methods(), error=error)
[docs] def POST(self, *args, **kwargs): """ Checks if user is authenticated and calls POST_AUTH or performs login and calls GET_AUTH. Otherwise, returns the login template. """ if session.loggedin: if not session.username and not self.__class__.__name__ == "ProfilePage": return redirect(url_for("profilepage")) if not self.is_lti_page and session.is_lti: # lti session self.user_manager.disconnect_user() return render_template("auth.html", auth_methods=self.user_manager.get_auth_methods()) return self.POST_AUTH(*args, **kwargs) else: user_input = request.form if "login" in user_input and "password" in user_input: if self.user_manager.auth_user(user_input["login"].strip(), user_input["password"]) is not None: return self.GET_AUTH(*args, **kwargs) else: return render_template("auth.html", auth_methods=self.user_manager.get_auth_methods(), error=_("Invalid login/password")) elif self.preview_allowed(*args, **kwargs): return self.POST_AUTH(*args, **kwargs) else: return render_template("auth.html", auth_methods=self.user_manager.get_auth_methods())
[docs] def preview_allowed(self, *args, **kwargs): """ If this function returns True, the auth check is disabled. Override this function with a custom check if needed. """ return False
[docs] class INGIniousAdministratorPage(INGIniousAuthPage): """ Augmented version of INGIniousAuthPage that checks if user is administrator (superadmin). """
[docs] def GET(self, *args, **kwargs): """ Checks if user is superadmin and calls GET_AUTH or performs logout. Otherwise, returns the login template. """ username = session.username if session.loggedin: if not self.user_manager.user_is_superadmin(username): return render_template("forbidden.html", message=_("Forbidden")) return self.GET_AUTH(*args, **kwargs) return INGIniousAuthPage.GET(self, *args, **kwargs)
[docs] def POST(self, *args, **kwargs): """ Checks if user is superadmin and calls POST_AUTH. Otherwise, returns the forbidden template. """ username = session.username if session.loggedin and self.user_manager.user_is_superadmin(username): return self.POST_AUTH() return render_template("forbidden.html", message=_("You have not sufficient right to see this part."))
[docs] class SignInPage(INGIniousAuthPage):
[docs] def GET_AUTH(self, *args, **kwargs): return redirect(url_for("mycoursespage"))
[docs] def POST_AUTH(self, *args, **kwargs): return redirect(url_for("mycoursespage"))
[docs] def GET(self): return INGIniousAuthPage.GET(self)
[docs] class LogOutPage(INGIniousAuthPage):
[docs] def GET_AUTH(self, *args, **kwargs): self.user_manager.disconnect_user() return redirect(url_for("courselistpage"))
[docs] def POST_AUTH(self, *args, **kwargs): self.user_manager.disconnect_user() return redirect(url_for("courselistpage"))
[docs] class INGIniousStaticPage(INGIniousPage): cache = {}
[docs] def GET(self, pageid): return self.show_page(pageid)
[docs] def POST(self, pageid): return self.show_page(pageid)
[docs] def show_page(self, page): static_directory = current_app.config["STATIC_DIRECTORY"] language = session.language # Check for the file filename = None mtime = None filepaths = [os.path.join(static_directory, page + ".yaml"), os.path.join(static_directory, page + "." + language + ".yaml")] for filepath in filepaths: if os.path.exists(filepath): filename = filepath mtime = os.stat(filepath).st_mtime if not filename: raise NotFound(description=_("File doesn't exist.")) # Check and update cache if INGIniousStaticPage.cache.get(filename, (0, None))[0] < mtime: with open(filename, "r") as f: INGIniousStaticPage.cache[filename] = mtime, custom_yaml.load(f) filecontent = INGIniousStaticPage.cache[filename][1] title = filecontent["title"] content = ParsableText.rst(filecontent["content"], initial_header_level=2) return render_template("static.html", pagetitle=title, content=content)