Example #1
0
def edit_content(course, content_path, TOC: TableOfContent):
    try:
        content = TOC.get_page_from_path("%s.rst" % content_path)
    except ContentNotFoundError:
        content = TOC.get_content_from_path(content_path)
    if request.method == "POST":
        inpt = request.form
        if "new_content" not in inpt:
            return seeother(request.path)
        else:
            if type(content) is Chapter:
                with open(
                        safe_join(syllabus.get_pages_path(course),
                                  content.path, "chapter_introduction.rst"),
                        "w") as f:
                    f.write(inpt["new_content"])
            else:
                with open(
                        safe_join(syllabus.get_pages_path(course),
                                  content.path), "w") as f:
                    f.write(inpt["new_content"])
            return seeother(request.path)
    elif request.method == "GET":
        return render_template("edit_page.html",
                               content_data=get_content_data(course, content),
                               content=content,
                               TOC=TOC)
def log_in():
    if request.method == "GET":
        return render_template("login.html", auth_methods=syllabus.get_config()['authentication_methods'],
                               feedback=pop_feeback(session, feedback_type="login"))
    if request.method == "POST":
        if "local" not in syllabus.get_config()['authentication_methods']:
            abort(404)
        inpt = request.form
        email = inpt["email"]
        password = inpt["password"]
        try:
            password_hash = hash_password_func(email=email, password=password,
                                               global_salt=syllabus.get_config().get('password_salt', None),
                                               n_iterations=syllabus.get_config().get('password_hash_iterations', 100000))
        except UnicodeEncodeError:
            # TODO: log
            return seeother("/login")

        user = User.query.filter(User.email == email).first()
        if user is None or user.hash_password != password_hash:
            set_feedback(session, ErrorFeedback("Invalid email/password."), feedback_type="login")
            return seeother("/login")
        if not user.activated:
            set_feedback(session, ErrorFeedback("Your account is not yet activated."), feedback_type="login")
            return seeother("/login")
        session['user'] = user.to_dict()
        return seeother(session.get("last_visited", "/"))
def config_edition():
    if request.method == "POST":
        inpt = request.form
        if "new_config" in inpt:
            try:
                # check YAML validity
                config = yaml.load(inpt["new_config"])
                # TODO: check that the configuration has the appropriate fields
                # update the config
                old_config = syllabus.get_config()
                syllabus.set_config(inpt["new_config"])
                # sync the git repo if it has changed
                try:
                    if (
                            "git" not in old_config["pages"]
                            and "git" in config["pages"]
                    ) or old_config["pages"]["git"] != config["pages"]["git"]:
                        syllabus.utils.pages.init_and_sync_repo(
                            force_sync=True)
                except KeyError as e:
                    pass
                except AttributeError:
                    return seeother(
                        request.path,
                        Feedback(feedback_type="error",
                                 message="The git repository has "
                                 "failed to synchronize. "
                                 "Please check your "
                                 "configuration."))
                return seeother(
                    request.path,
                    Feedback(feedback_type="success",
                             message="The table of contents has been"
                             " modified successfully !"))
            except yaml.YAMLError:
                return seeother(
                    request.path,
                    feedback=Feedback(
                        feedback_type="error",
                        message="The submitted configuration is not "
                        "written in valid YAML."))
    else:
        params = Params.query.one()
        config = syllabus.get_config()
        hook_path = ("/update_pages/%s" %
                     params.git_hook_url) if "git" in config[
                         "pages"] and params.git_hook_url is not None else None
        try:
            with open(syllabus.get_config_path(), 'r') as f:
                return render_template(
                    'edit_configuration.html',
                    active_element=sidebar['active_element'],
                    sidebar_elements=sidebar['elements'],
                    config=f.read(),
                    hook_path=hook_path,
                    feedback=pop_feeback(session))
        except TemplateNotFound:
            abort(404)
Example #4
0
def toc_edition(course):
    if not course in syllabus.get_config()["courses"].keys():
        abort(404)
    toc = syllabus.get_toc(course)
    if toc.ignored and not has_feedback(session):
        set_feedback(
            session,
            Feedback(feedback_type="warning",
                     message="The following contents have not been found :\n" +
                     "<pre>" + "\n".join(toc.ignored) + "</pre>"))
    if request.method == "POST":
        inpt = request.form
        if "new_content" in inpt:
            try:
                # check YAML validity
                toc_dict = yaml.load(inpt["new_content"],
                                     OrderedDictYAMLLoader)
                if not TableOfContent.is_toc_dict_valid(
                        syllabus.get_pages_path(course), toc_dict):
                    set_feedback(
                        session,
                        Feedback(feedback_type="error",
                                 message="The submitted table of contents "
                                 "is not consistent with the files "
                                 "located in the pages directory."))
                    return seeother(request.path)
            except yaml.YAMLError:
                set_feedback(
                    session,
                    Feedback(feedback_type="error",
                             message="The submitted table of contents is not "
                             "written in valid YAML."))
                return seeother(request.path)
            # the YAML is valid, write it in the ToC
            with open(
                    os.path.join(syllabus.get_pages_path(course), "toc.yaml"),
                    "w") as f:
                f.write(inpt["new_content"])
            syllabus.get_toc(course, force=True)
        set_feedback(
            session,
            Feedback(feedback_type="success",
                     message="The table of contents has been modified "
                     "successfully !"))
        return seeother(request.path)
    else:
        with open(os.path.join(syllabus.get_pages_path(course), "toc.yaml"),
                  "r") as f:
            try:
                return render_template(
                    'edit_table_of_content.html',
                    active_element=sidebar['active_element'],
                    sidebar_elements=sidebar['elements'],
                    content=f.read(),
                    feedback=pop_feeback(session))
            except TemplateNotFound:
                abort(404)
Example #5
0
def delete_content(course, inpt, TOC):
    pages_path = syllabus.get_pages_path(course)
    content_path = inpt["content-path"]
    path = os.path.join(pages_path, content_path)

    content_to_delete = TOC.get_content_from_path(content_path)
    TOC.remove_content_from_toc(content_to_delete)

    # dump the TOC and reload it
    syllabus.save_toc(course, TOC)
    syllabus.get_toc(course, force=True)

    # remove the files if asked
    if inpt.get("delete-files", None) == "on":
        if type(content_to_delete) is Chapter:
            # delete a chapter
            shutil.rmtree(os.path.join(path))
        else:
            # delete a page
            os.remove(path)

    set_feedback(
        session,
        Feedback(feedback_type="success",
                 message="The content has been successfully deleted"))
    return seeother(request.path)
Example #6
0
def update_pages(secret, course):
    params = Params.query.one()
    if secret != params.git_hook_url or 'git' not in syllabus.get_config(
    )['courses'][course]['pages']:
        return seeother("/")
    syllabus.utils.pages.init_and_sync_repo(course, force_sync=True)
    return "done"
Example #7
0
def users():
    if request.method == 'POST':
        inpt = request.form
        if inpt["action"] == "change_right":
            user = User.query.filter(User.username == inpt["username"]).first()
            if user.username == session["user"]["username"]:
                return seeother(request.path)
            user.right = "admin" if "admin" in inpt and inpt["admin"] == "on" else None
            db_session.commit()
            return seeother(request.path, SuccessFeedback("The rights of %s have been successfully edited" % user.username))
        return seeother(request.path)
    try:
        return render_template('users.html', active_element=sidebar['active_element'],
                               sidebar_elements=sidebar['elements'], users=User.query.all(),
                               feedback=pop_feeback(session))
    except TemplateNotFound:
        abort(404)
Example #8
0
def log_out():
    if "user" in session:
        saml = session["user"].get("login_method", None) == "saml"
        session.pop("user", None)
        if saml and "singleLogoutService" in saml_config["sp"]:
            req = prepare_request(request)
            auth = init_saml_auth(req, saml_config)
            return redirect(auth.logout())
    return seeother('/')
def log_in():
    if request.method == "GET":
        return render_template("login.html", auth_methods=syllabus.get_config()['authentication_methods'])
    if request.method == "POST":
        inpt = request.form
        username = inpt["username"]
        password = inpt["password"]
        try:
            password_hash = hash_password(password.encode("utf-8"))
        except UnicodeEncodeError:
            # TODO: log
            return seeother("/login")

        user = User.query.filter(User.username == username).first()
        if user is None or user.hash_password != password_hash:
            abort(403)
        session['user'] = user.to_dict()
        return seeother('/')
def reset_password(secret):
    user = db_session.query(User).filter(User.change_password_url == secret).first()
    if user is None:
        # TODO: log
        return seeother("/")
    if request.method == "GET":
        return render_template("reset_password.html", alert_hidden=True)
    if request.method == "POST":
        inpt = request.form
        password = inpt["password"]
        password_confirm = inpt["password_confirm"]
        if password != password_confirm:
            return render_template("reset_password.html", alert_hidden=False)
        password_hash = hash_password(password.encode("utf-8"))
        user.hash_password = password_hash
        user.change_password_url = None
        db_session.commit()
        return seeother("/login")
Example #11
0
def saml():
    if "saml" not in syllabus.get_config()['authentication_methods']:
        abort(404)
    req = prepare_request(request)
    req['request_uri'] = request.path  # hack to ensure to have the correct path and to avoid RelayState loops
    auth = init_saml_auth(req, saml_config)

    # if 'sso' in request.args:
    #     return
    if request.method == "GET":
        return redirect(auth.login())
    else:
        auth.process_response()
        errors = auth.get_errors()
        # Try and check if IdP is using several signature certificates
        # This is a limitation of python3-saml
        for cert in saml_config["idp"].get("additionalX509certs", []):
            if auth.get_last_error_reason(
            ) == "Signature validation failed. SAML Response rejected":
                import copy
                # Change used IdP certificate
                new_settings = copy.deepcopy(saml_config)
                new_settings["idp"]["x509cert"] = cert
                # Retry processing response
                auth = init_saml_auth(req, new_settings)
                auth.process_response()
                errors = auth.get_errors()
        if len(errors) == 0:
            attrs = auth.get_attributes()
            # session['samlNameId'] = auth.get_nameid()
            # session['samlSessionIndex'] = auth.get_session_index()

            username = attrs[saml_config['sp']['attrs']['username']][0]
            realname = attrs[saml_config['sp']['attrs']['realname']][0]
            email = attrs[saml_config['sp']['attrs']['email']][0]

            user = User.query.filter(User.email == email).first()

            if user is None:  # The user does not exist in our DB
                user = User(name=username,
                            full_name=realname,
                            email=email,
                            hash_password=None,
                            change_password_url=None)
                db_session.add(user)
                db_session.commit()

            session["user"] = user.to_dict()
            session["user"].update({"login_method": "saml"})

            self_url = OneLogin_Saml2_Utils.get_self_url(req)
            if 'RelayState' in request.form and self_url != request.form[
                    'RelayState']:
                return redirect(auth.redirect_to(request.form['RelayState']))

    return seeother("/")
def reset_password(secret):
    user = db_session.query(User).filter(User.change_password_url == secret).first()
    if user is None:
        # TODO: log
        return seeother("/")
    if request.method == "GET":
        return render_template("reset_password.html", alert_hidden=True)
    if request.method == "POST":
        inpt = request.form
        password = inpt["password"]
        password_confirm = inpt["password_confirm"]
        if password != password_confirm:
            return render_template("reset_password.html", alert_hidden=False)
        password_hash = hash_password_func(email=user.email, password=password,
                                           global_salt=syllabus.get_config().get('password_salt', None),
                                           n_iterations=syllabus.get_config().get('password_hash_iterations', 100000))
        user.hash_password = password_hash
        user.change_password_url = None
        db_session.commit()
        return seeother("/login")
def log_out():
    if "user" in session:
        saml = session["user"].get("login_method", None) == "saml"
        session.pop("user", None)
        if saml and "singleLogoutService" in saml_config["sp"]:
            try:
                req = prepare_request(request)
                auth = init_saml_auth(req, saml_config)
                return redirect(auth.logout())
            except OneLogin_Saml2_Error:
                pass
    return seeother(session.get("last_visited", "/"))
Example #14
0
def sphinx_rebuild(course, course_config):
    if course_config.get("sphinx"):
        syllabus.get_sphinx_build(course, True)
        set_feedback(
            session,
            Feedback(feedback_type="success",
                     message="The syllabus has been successfully rebuilt"))
    else:
        set_feedback(
            session,
            Feedback(feedback_type="error",
                     message="The syllabus is not a sphinx syllabus"))
    return seeother(request.path)
def course_index(course, print_mode=False):
    if not course in syllabus.get_config()["courses"].keys():
        abort(404)
    session["course"] = course
    course_config = syllabus.get_config()["courses"][course]
    if course_config.get("sphinx"):
        return seeother("/syllabus/{}/{}".format(course, course_config["sphinx"].get("index_page", "index.html")))
    try:
        TOC = syllabus.get_toc(course)
        if request.args.get("edit") is not None:
            return edit_content(course, TOC.index.path, TOC)
        print_mode = print_mode or request.args.get("print") is not None
        # only display the button to print the whole syllabus in the index
        return render_web_page(course, TOC.index, print_mode=print_mode, display_print_all=True)
    except ContentNotFoundError:
        abort(404)
def saml():
    if "saml" not in syllabus.get_config()['authentication_methods']:
        abort(404)
    req = prepare_request(request)
    req['request_uri'] = request.path  # hack to ensure to have the correct path and to avoid RelayState loops
    auth = init_saml_auth(req, saml_config)

    # if 'sso' in request.args:
    #     return
    if request.method == "GET":
        return redirect(auth.login())
    elif 'acs' in request.args:
        auth.process_response()
        errors = auth.get_errors()
        if len(errors) == 0:
            attrs = auth.get_attributes()
            # session['samlNameId'] = auth.get_nameid()
            # session['samlSessionIndex'] = auth.get_session_index()

            username = attrs[saml_config['sp']['attrs']['username']][0]
            realname = attrs[saml_config['sp']['attrs']['realname']][0]
            email = attrs[saml_config['sp']['attrs']['email']][0]

            user = User.query.filter(User.email == email).first()

            if user is None:  # The user does not exist in our DB
                user = User(name=username,
                            full_name=realname,
                            email=email,
                            hash_password=None,
                            change_password_url=None)
                db_session.add(user)
                db_session.commit()

            session["user"] = user.to_dict()
            session["user"].update({"login_method": "saml"})

            self_url = OneLogin_Saml2_Utils.get_self_url(req)
            if 'RelayState' in request.form and self_url != request.form[
                    'RelayState']:
                return redirect(auth.redirect_to(request.form['RelayState']))

    return seeother("/")
def activate_account():
    email = request.args.get('email')
    mac = request.args.get('token')
    try:
        ts = int(request.args.get('ts'))
    except TypeError:
        abort(404)
        return None
    email_activation_config = syllabus.get_config()["authentication_methods"]["local"].get("email_activation", {})
    if not verify_activation_mac(email=email, secret=email_activation_config["secret"],
                                 mac_to_verify=mac, timestamp=ts):
        # the mac does not match the provided email
        abort(404)
        return None

    if User.query.filter(User.email == email).count() != 0:
        set_feedback(session, ErrorFeedback("This user is already activated."), feedback_type="login")
        return seeother("/login")

    if request.method == "GET":
        return render_template("local_register_confirmed_account.html",
                               feedback=pop_feeback(session, feedback_type="login"))

    if request.method == "POST":
        inpt = request.form
        try:
            u = handle_user_registration_infos(inpt, email, False)
        except UnicodeEncodeError:
            # TODO: log
            return seeother("/login")

        if u is None:
            return seeother("/activate?{}".format(urllib.parse.urlencode({"email": email, "token": mac, "ts": ts})))

        try:
            locally_register_new_user(u, activated=True)
            feedback_message = "You have been successfully registered."
            set_feedback(session, SuccessFeedback(feedback_message), feedback_type="login")
            return seeother("/login")
        except UserAlreadyExists:
            db_session.rollback()
            set_feedback(session, ErrorFeedback("Could not register: this user already exists."), feedback_type="login")
            return seeother("/register/{}/{}".format(email, mac))

    set_feedback(session, SuccessFeedback("Your account has been successfully activated"), feedback_type="login")
    return seeother("/login")
Example #18
0
def content_edition(course):
    if not course in syllabus.get_config()["courses"].keys():
        abort(404)
    TOC = syllabus.get_toc(course)
    if TOC.ignored and not has_feedback(session):
        set_feedback(
            session,
            Feedback(feedback_type="warning",
                     message="The following contents have not been found :\n" +
                     "<pre>" + "\n".join(TOC.ignored) + "</pre>"))
    if request.method == "POST":
        inpt = request.form
        if inpt["action"] == "delete_content":
            return delete_content(course, inpt, TOC)
        containing_chapter = None
        try:
            if inpt["containing-chapter"] != "":
                containing_chapter = TOC.get_chapter_from_path(
                    inpt["containing-chapter"])

        except ContentNotFoundError:
            set_feedback(
                session,
                Feedback(feedback_type="error",
                         message="The containing chapter for this page "
                         "does not exist"))
            return seeother(request.path)

        # sanity checks on the filename: refuse empty filenames of filenames with spaces
        if len(inpt["name"]) == 0 or len(re.sub(
                r'\s+', '', inpt["name"])) != len(inpt["name"]):
            set_feedback(
                session,
                Feedback(
                    feedback_type="error",
                    message='The file name cannot be empty or contain spaces.')
            )
            return seeother(request.path)

        if os.sep in inpt["name"]:
            set_feedback(
                session,
                Feedback(
                    feedback_type="error",
                    message=
                    'The file name cannot contain the "%s" forbidden character.'
                    % os.sep))
            return seeother(request.path)

        pages_path = syllabus.get_pages_path()
        content_path = os.path.join(
            containing_chapter.path,
            inpt["name"]) if containing_chapter is not None else inpt["name"]
        path = os.path.join(pages_path, content_path)

        # ensure that we do not create a page at the top level
        if containing_chapter is None and inpt["action"] == "create_page":
            set_feedback(
                session,
                Feedback(feedback_type="error",
                         message="Pages cannot be at the top level of the "
                         "syllabus."))
            return seeother(request.path)

        # check that there is no chapter with the same title in the containing chapter
        if containing_chapter is None:
            for content in TOC.get_top_level_content():
                if content.title == inpt["title"]:
                    set_feedback(
                        session,
                        Feedback(feedback_type="error",
                                 message="There is already a top level "
                                 "chapter with this title."))
                    return seeother(request.path)
        else:
            for content in TOC.get_direct_content_of(containing_chapter):
                if content.title == inpt["title"]:
                    set_feedback(
                        session,
                        Feedback(feedback_type="error",
                                 message="There is already a chapter/page "
                                 "with this title in this chapter."))
                    return seeother(request.path)

        if inpt["action"] == "create_page":

            # add the extension to the page filename
            content_path += ".rst"
            path += ".rst"

            # when file/directory already exists
            if os.path.isdir(path) or os.path.isfile(path):
                set_feedback(
                    session,
                    Feedback(feedback_type="error",
                             message="A file or directory with this name "
                             "already exists."))
                return seeother(request.path)

            # create a new page
            open(path, "w").close()
            page = Page(path=content_path,
                        title=inpt["title"],
                        pages_path=syllabus.get_pages_path(course))
            TOC.add_content_in_toc(page)
        elif inpt["action"] == "create_chapter":
            # creating a new chapter
            try:
                os.mkdir(path)
            except FileExistsError:
                set_feedback(
                    session,
                    Feedback(feedback_type="error",
                             message="A file or directory with this name "
                             "already exists."))
                return seeother(request.path)
            chapter = Chapter(path=content_path,
                              title=inpt["title"],
                              pages_path=syllabus.get_pages_path(course))
            TOC.add_content_in_toc(chapter)

        # dump the TOC and reload it
        syllabus.save_toc(course, TOC)
        syllabus.get_toc(course, force=True)
        return seeother(request.path)
    try:
        return render_template('content_edition.html',
                               active_element=sidebar['active_element'],
                               course_str=course,
                               sidebar_elements=sidebar['elements'],
                               TOC=TOC,
                               feedback=pop_feeback(session))
    except TemplateNotFound:
        abort(404)
def register():
    if "local" not in syllabus.get_config()['authentication_methods']:
        abort(404)
    timestamp = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
    email_activation_config = syllabus.get_config()["authentication_methods"]["local"].get("email_activation", {})
    activation_required = email_activation_config.get("required", True)
    if request.method == "GET":
        return render_template("register.html", auth_methods=syllabus.get_config()['authentication_methods'],
                               feedback=pop_feeback(session, feedback_type="login"),
                               activation_required=activation_required)
    if request.method == "POST":
        inpt = request.form
        if activation_required:
            # send the email confirmation
            email = inpt["email"]
            auth_config = email_activation_config.get("authentication", {})
            parsed_url = urllib.parse.urlparse(urllib.parse.urljoin(request.host_url, "activate"))
            parsed_url = parsed_url._replace(query=urllib.parse.urlencode({
                "email": email,
                "token": get_activation_mac(email=email, secret=email_activation_config["secret"], timestamp=timestamp),
                "ts": timestamp
            }))
            url = urllib.parse.urlunparse(parsed_url)
            if auth_config.get("required", False):
                send_authenticated_confirmation_mail(email_activation_config["sender_email_address"], email,
                                                     url,
                                                     email_activation_config["smtp_server"],
                                                     username=auth_config["username"],
                                                     password=auth_config["password"],
                                                     smtp_port=email_activation_config["smtp_server_port"])
            else:
                send_confirmation_mail(email_activation_config["sender_email_address"], email,
                                       url,
                                       email_activation_config["smtp_server"],
                                       use_ssl=email_activation_config["use_ssl"],
                                       smtp_port=email_activation_config["smtp_server_port"])
            feedback_message = "Registration successful. Please activate your account using the activation link you received by e-mail." \
                               "Click <a href=\"/login\">here</a> to log in."
            set_feedback(session, SuccessFeedback(feedback_message), feedback_type="login")
            return seeother("/activation_needed")

        # here, the activation is not required

        try:
            u = handle_user_registration_infos(inpt, inpt["email"], False)
        except UnicodeEncodeError:
            # TODO: log
            return seeother("/login")

        if u is None:
            return seeother("/register")

        try:
            locally_register_new_user(u, not activation_required)
            feedback_message = "You have been successfully registered."
            set_feedback(session, SuccessFeedback(feedback_message), feedback_type="login")
            return seeother("/login")
        except UserAlreadyExists as e:
            set_feedback(session, ErrorFeedback("Could not register: this user already exists{}.".format(
                                                (": %s" % e.reason) if e.reason is not None else "")),
                         feedback_type="login")
            db_session.rollback()
            return seeother("/register")