Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
db47112
Update svg lib
Mar 24, 2022
a07239a
Add logout logic
Mar 24, 2022
93197e0
Add revoked token logic
Mar 24, 2022
44bdc52
Update sample config
Mar 24, 2022
59160da
Rewrite execution logic
Mar 24, 2022
f0a70cf
Add generic exercise model
Mar 24, 2022
7c7f794
Fix json errors
Mar 24, 2022
ceb1edf
Properly extract post data
Mar 24, 2022
50b166a
Linting
Mar 24, 2022
be1387b
Add Exercise Model and rename Execution Model
Mar 25, 2022
5d890cb
Add generic add-execution-db-entry function
Mar 25, 2022
43ff0cc
Simplify execution endpoint
Mar 25, 2022
11eeab4
Add mail sender and receiver in config
Mar 25, 2022
8a7eb2f
Remove CORS and fix JWT location
Mar 25, 2022
e2a81d6
Set default JWT Location
Mar 26, 2022
63976eb
Add get history logic
Mar 26, 2022
e0f4867
Automatically generate timestamp
Mar 26, 2022
6f1bc57
Adapt File-structure
Mar 26, 2022
1deae7a
Outsource DB Models
Mar 26, 2022
51841a0
Rename attribute
Mar 27, 2022
5c09616
Add DB helper functions
Mar 27, 2022
dd626a8
Outsource history creation
Mar 27, 2022
e5278d4
Outsource table creation
Mar 27, 2022
b0236f8
Implement admin endpoints
Mar 27, 2022
68d794f
Update Output
Mar 27, 2022
4f78c69
Add linting
Mar 27, 2022
3d46262
Fix Results output
Mar 28, 2022
991711d
Use logger instead of print
Mar 28, 2022
3545c6e
Remove prints
Mar 28, 2022
6b6136c
Add linting
Mar 28, 2022
d70526f
Assign vnc clients to template
Mar 28, 2022
866b218
Fix typo
Mar 28, 2022
74bf40d
Allow jwt in headers
Mar 28, 2022
49a1a1f
Change blueprint imports
Mar 28, 2022
bed60ce
Add admin nav
Mar 28, 2022
3302325
Rename access token
Mar 29, 2022
3fe0811
Add title to exercises
Mar 29, 2022
9599955
Fix complete-state bug
Mar 29, 2022
24d9a4f
Rename access token
Mar 29, 2022
8f6a007
Add execution-state endpoint
Mar 29, 2022
4acaf3d
Add parent exercise indicator
Mar 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions learners/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

from flask import Flask
from flask_cors import CORS

import os
from learners.logger import logger


Expand Down Expand Up @@ -32,11 +32,15 @@ def main():

init_mail(app)

CORS(app)

from learners import views
import learners.routes as routes

app.register_blueprint(views.bp)
app.register_blueprint(routes.home_api)
app.register_blueprint(routes.authentication_api)
app.register_blueprint(routes.interface_api)
app.register_blueprint(routes.execution_api)
app.register_blueprint(routes.callback_api)
app.register_blueprint(routes.statics_api)
app.register_blueprint(routes.admin_api)

return app

Expand Down
45 changes: 24 additions & 21 deletions learners/conf/config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import json
import os
from datetime import timedelta

from flask_assets import Environment
from learners import logger
from learners.assets import get_bundle

from strictyaml import load, YAMLError

from strictyaml import YAMLError, load

cfg = None

Expand Down Expand Up @@ -39,10 +39,10 @@ def __init__(self):
yaml_config = stream.read()
learners_config = load(yaml_config, config_schema).data
except YAMLError as yamlerr:
print(yamlerr)
logger.exception(yamlerr)
raise
except EnvironmentError as enverr:
print(enverr)
logger.exception(enverr)
raise

# Set learners configuration
Expand All @@ -67,6 +67,8 @@ def __init__(self):
self.mail_password = learners_config.get("mail").get("password")
self.mail_tls = learners_config.get("mail").get("tls")
self.mail_ssl = learners_config.get("mail").get("ssl")
self.mail_sender = learners_config.get("mail").get("sender_name")
self.mail_recipients = learners_config.get("mail").get("recipients")
elif os.getenv("MAIL"):
self.mail = True
self.mail_server = os.getenv("MAIL_SERVER") or ""
Expand All @@ -75,14 +77,16 @@ def __init__(self):
self.mail_password = os.getenv("MAIL_PASSWORD") or ""
self.mail_tls = os.getenv("MAIL_TLS") or True
self.mail_ssl = os.getenv("MAIL_SSL") or False
self.mail_sender = os.getenv("MAIL_SENDER_NAME") or self.mail_username
self.mail_recipients = os.getenv("MAIL_RECIPIENTS") or []
else:
self.mail = False

self.users = learners_config.get("users")

# Set components configuration
self.novnc = {"server": learners_config.get("novnc").get("server")}

self.users = learners_config.get("users")
self.users = json.loads(json.dumps(self.users).replace("default", self.novnc.get("server")))

self.callback = {"endpoint": learners_config.get("callback").get("endpoint")}

self.documentation = {
Expand All @@ -95,24 +99,27 @@ def __init__(self):
"endpoint": learners_config.get("exercises").get("endpoint"),
}

self.venjix = {"auth_secret": learners_config.get("venjix").get("auth_secret"), "url": learners_config.get("venjix").get("url")}
self.venjix = {
"auth_secret": learners_config.get("venjix").get("auth_secret"),
"url": learners_config.get("venjix").get("url"),
"headers": {
"Content-type": "application/json",
"Authorization": f"Bearer {learners_config.get('venjix').get('auth_secret')}",
},
}

# define the render template
self.template = {
"authenticated": False,
"chat": False,
"admin": False,
"user_id": None,
"branding": self.branding,
"theme": self.theme,
"vnc_clients": None,
"url_documentation": None,
"url_exercises": None,
"url_documentation": f"{self.documentation.get('endpoint')}/{self.language}/index.html",
"url_exercises": f"{self.exercises.get('endpoint')}/{self.language}/index.html",
}

# set CORS configuration
self.cors_origins = []
self.cors_origins.append(self.venjix.get("url"))


def build_config(app):

Expand All @@ -125,7 +132,6 @@ def build_config(app):
global cfg
cfg = Configuration()

# Set flask app.config
config_app(app)


Expand All @@ -145,10 +151,7 @@ def config_app(app):
app.config["JWT_SECRET_KEY"] = cfg.jwt_secret_key
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = cfg.jwt_access_token_expires
app.config["JWT_TOKEN_LOCATION"] = ["headers", "cookies"]

app.config["CORS_HEADERS"] = "Content-Type"
app.config["CORS_ORIGINS"] = cfg.cors_origins
app.config["CORS_SUPPORTS_CREDENTIALS"] = True
app.config["JWT_ACCESS_COOKIE_NAME"] = "access_token_cookie"

if cfg.mail:
app.config["MAIL_SERVER"] = cfg.mail_server
Expand Down
4 changes: 3 additions & 1 deletion learners/conf/config_schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from strictyaml import Map, Str, Int, Seq, Bool, Any, Optional, MapPattern, EmptyDict, EmptyNone
from strictyaml import Map, Str, Int, Seq, Bool, Any, Optional, MapPattern, EmptyDict, EmptyNone, EmptyList

config_schema = Map(
{
Expand Down Expand Up @@ -30,6 +30,8 @@
Optional("password", default=""): Str(),
Optional("tls", default=True): Bool(),
Optional("ssl", default=False): Bool(),
Optional("sender_name", default=""): Str(),
Optional("recipients"): EmptyList() | Seq(Str()),
}
),
"users": MapPattern(
Expand Down
42 changes: 42 additions & 0 deletions learners/conf/db_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# sourcery skip: avoid-builtin-shadow
from learners.database import db
from sqlalchemy import func


class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)
executions = db.relationship("Execution", backref="user", lazy=True)


class Execution(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(120), nullable=False)
script = db.Column(db.String(120), nullable=True)
execution_timestamp = db.Column(db.DateTime, nullable=False, default=func.current_timestamp())
response_timestamp = db.Column(db.DateTime, nullable=True)
response_content = db.Column(db.Text, nullable=True)
form_data = db.Column(db.String(), nullable=True)
msg = db.Column(db.String(240), nullable=True)
uuid = db.Column(db.String(120), unique=True, nullable=True)
completed = db.Column(db.Integer, nullable=False, default=0)
connection_failed = db.Column(db.Integer, nullable=False, default=0)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
exercise_id = db.Column(db.Integer, db.ForeignKey("exercise.id"), nullable=False)


class Exercise(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(120), nullable=False)
name = db.Column(db.String(120), unique=True, nullable=False)
pretty_name = db.Column(db.String(120), unique=True, nullable=False)
title = db.Column(db.String(120), nullable=True)
parent = db.Column(db.String(120), nullable=True)
weight = db.Column(db.String(120), nullable=False)
executions = db.relationship("Execution", backref="exercise", lazy=True)


class TokenBlocklist(db.Model):
id = db.Column(db.Integer, primary_key=True)
jti = db.Column(db.String(36), nullable=False)
created_at = db.Column(db.DateTime, nullable=False)
48 changes: 4 additions & 44 deletions learners/database.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from learners.conf.config import cfg

from sqlalchemy import event


"""
Set up the database
Expand All @@ -14,47 +9,12 @@

db = SQLAlchemy()

# User table
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
scriptExercises = db.relationship("ScriptExercise", backref="user", lazy=True)
formExercises = db.relationship("FormExercise", backref="user", lazy=True)


# History of sent POSTs
class ScriptExercise(db.Model):
id = db.Column(db.Integer, primary_key=True)
script_name = db.Column(db.String(120), nullable=False)
call_uuid = db.Column(db.String(120), unique=True, nullable=False)
start_time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
response_time = db.Column(db.DateTime, nullable=True)
response_content = db.Column(db.Text, nullable=True)
completed = db.Column(db.Integer, nullable=False, default=0)
connection_failed = db.Column(db.Integer, nullable=False, default=0)
msg = db.Column(db.String(240), nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)


# Formdata
class FormExercise(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
data = db.Column(db.String(), nullable=False)
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)


@event.listens_for(User.__table__, "after_create")
def insert_initial_users(*args, **kwargs):
for user, _ in cfg.users.items():
db.session.add(User(username=user))

db.session.commit()


def build_db(app):
global db
db.init_app(app)

from learners.conf.db_models import Execution, Exercise, TokenBlocklist, User
from learners.functions.database import insert_exercises, insert_initial_users

db.init_app(app)
db.create_all()
Empty file added learners/functions/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions learners/functions/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def check_password(usermap: dict, user: str, password: str) -> bool:
return user in usermap and usermap.get(user).get("password") == password
Loading