# -*- 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)