Skip to content

Commit 4d9b57b

Browse files
author
git-lreuter
committed
Remove branding from learners
1 parent 14ab2be commit 4d9b57b

File tree

18 files changed

+247
-386
lines changed

18 files changed

+247
-386
lines changed

learners/conf/admin.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import json
2+
3+
from flask import Blueprint, jsonify, render_template
4+
from learners.conf.config import cfg
5+
from learners.functions.database import (
6+
get_all_exercises,
7+
get_all_users,
8+
get_executions_by_user_exercise,
9+
get_exercise_by_name,
10+
get_user_by_id,
11+
)
12+
from learners.functions.helpers import extract_history
13+
from learners.functions.results import construct_results_table
14+
from learners.jwt_manager import admin_required
15+
from learners.logger import logger
16+
17+
admin_api = Blueprint("admin_api", __name__)
18+
19+
20+
@admin_api.route("/admin", methods=["GET"])
21+
@admin_required()
22+
def admin_area():
23+
24+
exercises = get_all_exercises()
25+
users = get_all_users()
26+
27+
user_filter = [{"id": 0, "username": "all"}]
28+
user_filter.extend({"id": user.id, "username": user.name} for user in users)
29+
30+
exercises_filter = [{"id": "all", "name": "all"}]
31+
exercises_filter.extend({"id": exercise.name, "name": exercise.title} for exercise in exercises)
32+
33+
results_table = construct_results_table(exercises, users)
34+
return render_template("results.html", exercises=exercises_filter, users=user_filter, table=results_table, **cfg.template)
35+
36+
37+
@admin_api.route("/result/<user_id>/<exercise_name>", methods=["GET"])
38+
@admin_required()
39+
def get_exercise_result(user_id, exercise_name):
40+
41+
exercise = get_exercise_by_name(exercise_name)
42+
user = get_user_by_id(user_id)
43+
executions = get_executions_by_user_exercise(user_id, exercise.id)
44+
45+
last_execution = executions[0] if executions else None
46+
data = {
47+
"completed": any(execution.completed for execution in executions) if last_execution else False,
48+
"executed": any(not execution.connection_failed for execution in executions) if last_execution else False,
49+
"msg": last_execution.msg if last_execution else None,
50+
"response_timestamp": last_execution.response_timestamp if last_execution else None,
51+
"connection": any(not execution.connection_failed for execution in executions) if last_execution else False,
52+
}
53+
54+
if exercise.type == "form":
55+
data["form"] = json.loads(last_execution.form_data) if last_execution else None
56+
57+
data["history"] = extract_history(executions) if executions else None
58+
59+
return render_template("result_details.html", user=user.name, exercise=exercise.title, data=data, **cfg.template)

learners/conf/authentication.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from datetime import datetime, timezone
2+
3+
from flask import Blueprint, make_response, redirect, render_template, request
4+
from flask_jwt_extended import create_access_token, get_jwt, get_jwt_identity, jwt_required, verify_jwt_in_request
5+
from learners.conf.config import cfg
6+
from learners.conf.db_models import TokenBlocklist
7+
from learners.database import db
8+
from learners.functions.authentication import check_password
9+
10+
authentication_api = Blueprint("authentication_api", __name__)
11+
12+
13+
@authentication_api.route("/login", methods=["GET", "POST"])
14+
def login():
15+
16+
if request.method == "GET":
17+
try:
18+
verify_jwt_in_request()
19+
success_msg = f"Logged in as {get_jwt_identity()}."
20+
return render_template("login.html", **cfg.template, success=True, success_msg=success_msg)
21+
except Exception:
22+
return render_template("login.html", **cfg.template)
23+
24+
username = request.form.get("username", None)
25+
password = request.form.get("password", None)
26+
27+
if not check_password(cfg.users, username, password):
28+
error_msg = "Invalid username or password"
29+
return render_template("login.html", **cfg.template, error=error_msg)
30+
31+
admin = cfg.users.get(username).get("is_admin")
32+
cfg.template["admin"] = admin
33+
cfg.template["authenticated"] = True
34+
35+
access_token = create_access_token(identity=username, additional_claims={"is_admin": admin})
36+
response = make_response(redirect("/admin", 302)) if admin else make_response(redirect("/access", 302))
37+
response.set_cookie("access_token_cookie", value=access_token, secure=True, httponly=False)
38+
39+
return response
40+
41+
42+
@authentication_api.route("/logout")
43+
@jwt_required()
44+
def modify_token():
45+
jti = get_jwt()["jti"]
46+
now = datetime.now(timezone.utc)
47+
db.session.add(TokenBlocklist(jti=jti, created_at=now))
48+
db.session.commit()
49+
success_msg = "Successfully logged out."
50+
cfg.template["authenticated"] = False
51+
return render_template("login.html", **cfg.template, success_msg=success_msg)

learners/conf/config.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ def __init__(self):
108108
}
109109

110110
self.template = {
111-
"files": set_branded_template_files(self),
112111
"chat": False,
113112
"admin": False,
114113
"user_id": None,
@@ -163,20 +162,3 @@ def config_app(app):
163162
app.config["MAIL_PASSWORD"] = cfg.mail_password
164163
app.config["MAIL_USE_TLS"] = cfg.mail_tls
165164
app.config["MAIL_USE_SSL"] = cfg.mail_ssl
166-
167-
168-
def set_branded_template_files(cfg) -> dict:
169-
170-
template_files = {}
171-
dirs = ["", "partials/"]
172-
for dir in dirs:
173-
for file in os.listdir(f"./learners/templates/{dir}"):
174-
file_name, file_ext = os.path.splitext(file)
175-
if os.path.exists(f"./learners/templates/branding/{cfg.theme}_{file_name}{file_ext}"):
176-
file = f"branding/{cfg.theme}_{file}"
177-
else:
178-
file = f"{dir}{file}"
179-
if file.endswith(".html"):
180-
template_files[file_name] = file
181-
182-
return template_files

learners/conf/interface.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from flask import Blueprint, render_template
2+
from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required
3+
from learners.conf.config import cfg
4+
5+
interface_api = Blueprint("interface_api", __name__)
6+
7+
8+
@interface_api.route("/access", methods=["GET"])
9+
@jwt_required()
10+
def access():
11+
12+
user_id = get_jwt_identity()
13+
14+
if vnc_clients := cfg.users.get(user_id).get("vnc_clients"):
15+
cfg.template["vnc_clients"] = vnc_clients
16+
for client, details in vnc_clients.items():
17+
if cfg.jwt_for_vnc_access:
18+
additional_claims = {
19+
"target": str(details.get("target")),
20+
"username": str(details.get("username")),
21+
"password": str(details.get("password")),
22+
}
23+
vnc_auth_token = create_access_token(identity=user_id, additional_claims=additional_claims)
24+
auth_url = f"{details.get('server')}?auth={vnc_auth_token}"
25+
else:
26+
auth_url = (
27+
f"{details.get('server')}?"
28+
+ f"username={details.get('username')}&password={details.get('password')}&"
29+
+ f"target={details.get('target')}"
30+
)
31+
cfg.template["vnc_clients"][client].setdefault("url", auth_url)
32+
33+
return render_template("index.html", **cfg.template)

learners/conf/jwt_manager.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from functools import wraps
2+
3+
from flask import render_template
4+
from flask_jwt_extended import JWTManager, get_jwt, verify_jwt_in_request
5+
6+
from learners import logger
7+
from learners.conf.config import cfg
8+
from learners.conf.db_models import TokenBlocklist
9+
from learners.database import db
10+
11+
jwt = JWTManager()
12+
13+
14+
@jwt.expired_token_loader
15+
def token_expired(jwt_header, jwt_payload):
16+
error_msg = "Your token is expired. Please login again."
17+
cfg.template["admin"] = False
18+
cfg.template["authenticated"] = False
19+
return render_template("login.html", **cfg.template, error=error_msg)
20+
21+
22+
@jwt.invalid_token_loader
23+
def token_invalid(jwt_payload):
24+
error_msg = "Your token is invalid."
25+
cfg.template["admin"] = False
26+
cfg.template["authenticated"] = False
27+
return render_template("login.html", **cfg.template, error=error_msg)
28+
29+
30+
@jwt.token_verification_loader
31+
def token_valid(jwt_header, jwt_data):
32+
cfg.template["authenticated"] = True
33+
return jwt_data
34+
35+
36+
@jwt.unauthorized_loader
37+
def token_missing(callback):
38+
error_msg = "Authorization is missing."
39+
cfg.template["admin"] = False
40+
cfg.template["authenticated"] = False
41+
return render_template("login.html", **cfg.template, error=error_msg)
42+
43+
44+
@jwt.token_in_blocklist_loader
45+
def check_if_token_revoked(jwt_header, jwt_payload):
46+
try:
47+
jti = jwt_payload["jti"]
48+
token = db.session.query(TokenBlocklist.id).filter_by(jti=jti).scalar()
49+
return token is not None
50+
except Exception as e:
51+
logger.exception(e)
52+
return True
53+
54+
55+
@jwt.revoked_token_loader
56+
def token_revoked(jwt_header, jwt_payload):
57+
error_msg = "Token has been revoked."
58+
return render_template("login.html", **cfg.template, error=error_msg)
59+
60+
61+
def admin_required():
62+
def wrapper(fn):
63+
@wraps(fn)
64+
def decorator(*args, **kwargs):
65+
verify_jwt_in_request()
66+
67+
if get_jwt().get("is_admin"):
68+
cfg.template["admin"] = True
69+
cfg.template["authenticated"] = True
70+
return fn(*args, **kwargs)
71+
else:
72+
error_msg = "Admins only!"
73+
cfg.template["admin"] = False
74+
cfg.template["authenticated"] = False
75+
return render_template("login.html", **cfg.template, error=error_msg)
76+
77+
return decorator
78+
79+
return wrapper
80+
81+
82+
def init_jwt(app):
83+
global jwt
84+
jwt.init_app(app)

learners/jwt_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ def token_expired(jwt_header, jwt_payload):
1616
error_msg = "Your token is expired. Please login again."
1717
cfg.template["admin"] = False
1818
cfg.template["authenticated"] = False
19-
return render_template(cfg.template.get("files").get("login"), **cfg.template, error=error_msg)
19+
return render_template("login.html", **cfg.template, error=error_msg)
2020

2121

2222
@jwt.invalid_token_loader
2323
def token_invalid(jwt_payload):
2424
error_msg = "Your token is invalid."
2525
cfg.template["admin"] = False
2626
cfg.template["authenticated"] = False
27-
return render_template(cfg.template.get("files").get("login"), **cfg.template, error=error_msg)
27+
return render_template("login.html", **cfg.template, error=error_msg)
2828

2929

3030
@jwt.token_verification_loader
@@ -38,7 +38,7 @@ def token_missing(callback):
3838
error_msg = "Authorization is missing."
3939
cfg.template["admin"] = False
4040
cfg.template["authenticated"] = False
41-
return render_template(cfg.template.get("files").get("login"), **cfg.template, error=error_msg)
41+
return render_template("login.html", **cfg.template, error=error_msg)
4242

4343

4444
@jwt.token_in_blocklist_loader
@@ -55,7 +55,7 @@ def check_if_token_revoked(jwt_header, jwt_payload):
5555
@jwt.revoked_token_loader
5656
def token_revoked(jwt_header, jwt_payload):
5757
error_msg = "Token has been revoked."
58-
return render_template(cfg.template.get("files").get("login"), **cfg.template, error=error_msg)
58+
return render_template("login.html", **cfg.template, error=error_msg)
5959

6060

6161
def admin_required():
@@ -72,7 +72,7 @@ def decorator(*args, **kwargs):
7272
error_msg = "Admins only!"
7373
cfg.template["admin"] = False
7474
cfg.template["authenticated"] = False
75-
return render_template(cfg.template.get("files").get("login"), **cfg.template, error=error_msg)
75+
return render_template("login.html", **cfg.template, error=error_msg)
7676

7777
return decorator
7878

learners/routes/admin.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ def admin_area():
3131
exercises_filter.extend({"id": exercise.name, "name": exercise.title} for exercise in exercises)
3232

3333
results_table = construct_results_table(exercises, users)
34-
return render_template(
35-
cfg.template.get("files").get("results"), exercises=exercises_filter, users=user_filter, table=results_table, **cfg.template
36-
)
34+
return render_template("results.html", exercises=exercises_filter, users=user_filter, table=results_table, **cfg.template)
3735

3836

3937
@admin_api.route("/result/<user_id>/<exercise_name>", methods=["GET"])
@@ -58,6 +56,4 @@ def get_exercise_result(user_id, exercise_name):
5856

5957
data["history"] = extract_history(executions) if executions else None
6058

61-
return render_template(
62-
cfg.template.get("files").get("result_details"), user=user.name, exercise=exercise.title, data=data, **cfg.template
63-
)
59+
return render_template("result_details.html", user=user.name, exercise=exercise.title, data=data, **cfg.template)

learners/routes/authentication.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ def login():
1717
try:
1818
verify_jwt_in_request()
1919
success_msg = f"Logged in as {get_jwt_identity()}."
20-
return render_template(cfg.template.get("files").get("login"), **cfg.template, success=True, success_msg=success_msg)
20+
return render_template("login.html", **cfg.template, success=True, success_msg=success_msg)
2121
except Exception:
22-
return render_template(cfg.template.get("files").get("login"), **cfg.template)
22+
return render_template("login.html", **cfg.template)
2323

2424
username = request.form.get("username", None)
2525
password = request.form.get("password", None)
2626

2727
if not check_password(cfg.users, username, password):
2828
error_msg = "Invalid username or password"
29-
return render_template(cfg.template.get("files").get("login"), **cfg.template, error=error_msg)
29+
return render_template("login.html", **cfg.template, error=error_msg)
3030

3131
admin = cfg.users.get(username).get("is_admin")
3232
cfg.template["admin"] = admin
@@ -48,4 +48,4 @@ def modify_token():
4848
db.session.commit()
4949
success_msg = "Successfully logged out."
5050
cfg.template["authenticated"] = False
51-
return render_template(cfg.template.get("files").get("login"), **cfg.template, success_msg=success_msg)
51+
return render_template("login.html", **cfg.template, success_msg=success_msg)

learners/routes/interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ def access():
3030
)
3131
cfg.template["vnc_clients"][client].setdefault("url", auth_url)
3232

33-
return render_template(cfg.template.get("files").get("index"), **cfg.template)
33+
return render_template("index.html", **cfg.template)

0 commit comments

Comments
 (0)