From 64ba3029f148ea5f4a8e75e8e8f56095de059db8 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Mon, 5 Aug 2024 11:59:17 -0700 Subject: [PATCH 1/7] changing color to blue #61afef --- dojo_theme/static/css/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo_theme/static/css/custom.css b/dojo_theme/static/css/custom.css index 9fa8679a..29c48d41 100644 --- a/dojo_theme/static/css/custom.css +++ b/dojo_theme/static/css/custom.css @@ -359,7 +359,7 @@ pre { } code{ - color: #EE6889; + color: #61afef; } .hljs { From 7d072729478fb6ef2f6a1be3f6c65a4af9c6bdd4 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Mon, 5 Aug 2024 14:45:39 -0700 Subject: [PATCH 2/7] Added a secondary due date called extra late date, the purpose of the extra late date is to have a secondary more severe late penalty. For example, a late penalty might be 10% but the extra late penalty might be 50%. --- dojo_plugin/pages/course.py | 60 ++++++++++++++++++++------ dojo_theme/static/css/custom.css | 21 ++++++++- dojo_theme/templates/course.html | 8 +++- dojo_theme/templates/grades_admin.html | 6 +++ 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/dojo_plugin/pages/course.py b/dojo_plugin/pages/course.py index 3378adcb..5c19c39e 100644 --- a/dojo_plugin/pages/course.py +++ b/dojo_plugin/pages/course.py @@ -47,6 +47,7 @@ def grade(dojo, users_query, *, ignore_pending=False): continue assessment_dates[assessment["id"]][assessment["type"]] = ( datetime.datetime.fromisoformat(assessment["date"]).astimezone(datetime.timezone.utc), + datetime.datetime.fromisoformat(assessment.get("extra_late_date","3000-01-01T16:59:59-07:00")).astimezone(datetime.timezone.utc), assessment.get("extensions", {}), ) @@ -57,12 +58,21 @@ def dated_count(label, date_type): def query(module_id): if date_type not in assessment_dates[module_id]: return None - date, extensions = assessment_dates[module_id][date_type] + date, extra_late_date, extensions = assessment_dates[module_id][date_type] + if label == "extra_late_solves": + if extra_late_date is None: + return False + date = extra_late_date user_date = db.case( [(Solves.user_id == int(user_id), date + datetime.timedelta(days=days)) for user_id, days in extensions.items()], else_=date ) if extensions else date + if label == "late_solves": + + return and_(Solves.date >= user_date, Solves.date < extra_late_date) + elif label == "extra_late_solves": + return Solves.date >= user_date return Solves.date < user_date return db.func.sum( db.case([(DojoModules.id == module_id, cast(query(module_id), db.Integer)) @@ -84,6 +94,8 @@ def query(module_id): DojoModules.id.label("module_id"), dated_count("checkpoint_solves", "checkpoint"), dated_count("due_solves", "due"), + dated_count("late_solves", "due"), + dated_count("extra_late_solves", "due"), dated_count("all_solves", None) ) ).subquery() @@ -106,6 +118,8 @@ def result(user_id): date = datetime.datetime.fromisoformat(assessment["date"]) if type in ["checkpoint", "due"] else None if ignore_pending and date and date > now: continue + + extra_late_date = datetime.datetime.fromisoformat(assessment.get("extra_late_date",None)) if type in ["checkpoint", "due"] and "extra_late_date" in assessment else None if type == "checkpoint": module_id = assessment["id"] @@ -114,7 +128,7 @@ def result(user_id): extension = assessment.get("extensions", {}).get(str(user_id), 0) challenge_count = challenge_counts[module_id] - checkpoint_solves, due_solves, all_solves = module_solves.get(module_id, (0, 0, 0)) + checkpoint_solves, due_solves, late_solves, extra_late_solves, all_solves = module_solves.get(module_id, (0, 0, 0, 0, 0)) challenge_count_required = int(challenge_count * percent_required) user_date = date + datetime.timedelta(days=extension) @@ -131,25 +145,43 @@ def result(user_id): weight = assessment["weight"] percent_required = assessment.get("percent_required", 1.0) late_penalty = assessment.get("late_penalty", 0.0) + + extra_late_penalty = assessment.get("extra_late_penalty", 0.0) + extension = assessment.get("extensions", {}).get(str(user_id), 0) override = assessment.get("overrides", {}).get(str(user_id), None) challenge_count = challenge_counts[module_id] - checkpoint_solves, due_solves, all_solves = module_solves.get(module_id, (0, 0, 0)) - late_solves = all_solves - due_solves + checkpoint_solves, due_solves, late_solves, extra_late_solves, all_solves = module_solves.get(module_id, (0, 0, 0, 0, 0)) + + print("@"*50) + print(f"{checkpoint_solves=}, {due_solves=}, {late_solves=}, {extra_late_solves=}") + print("@"*50) + challenge_count_required = int(challenge_count * percent_required) user_date = date + datetime.timedelta(days=extension) + extra_late_user_date = None + if extra_late_date is not None: + extra_late_user_date = extra_late_date + datetime.timedelta(days=extension) late_value = 1 - late_penalty - max_late_solves = challenge_count_required - min(due_solves, challenge_count_required) - capped_late_solves = min(max_late_solves, late_solves) + extra_late_value = 1 - extra_late_penalty - if not late_solves: + max_late_solves = challenge_count_required - min(due_solves, challenge_count_required) + capped_late_solves = min(max_late_solves, late_solves) + capped_extra_late_solves = min(max_late_solves-capped_late_solves, extra_late_solves) + + if not late_solves and not extra_late_solves: progress = f"{due_solves} / {challenge_count_required}" - else: + elif late_solves and not extra_late_solves: progress = f"{due_solves} (+{late_solves}) / {challenge_count_required}" - + elif not late_solves and extra_late_solves: + progress = f"{due_solves} (+{extra_late_solves}) / {challenge_count_required}" + else: + progress = f"{due_solves} (+{late_solves}) (+{extra_late_solves}) / {challenge_count_required}" if override is None: - credit = min((due_solves + late_value * capped_late_solves) / challenge_count_required, 1.0) + late_points = late_value * capped_late_solves + extra_late_points = extra_late_value * capped_extra_late_solves + credit = min((due_solves + late_points + extra_late_points ) / challenge_count_required, 1.0) else: credit = override progress = f"{progress} *" @@ -157,6 +189,7 @@ def result(user_id): assessment_grades.append(dict( name=assessment_name(dojo, assessment), date=str(user_date) + (" *" if extension else ""), + extra_late_date=str(extra_late_user_date) + (" *" if extension else ""), weight=weight, progress=progress, credit=credit, @@ -192,11 +225,12 @@ def result(user_id): return dict(user_id=user_id, assessment_grades=assessment_grades, overall_grade=overall_grade, - letter_grade=letter_grade) + letter_grade=letter_grade, + show_extra_late_date= any(row.get('extra_late_date',None) is not None for row in assessments)) user_id = None previous_user_id = None - for user_id, module_id, checkpoint_solves, due_solves, all_solves in user_solves: + for user_id, module_id, checkpoint_solves, due_solves, late_solves, extra_late_solves, all_solves in user_solves: if user_id != previous_user_id: if previous_user_id is not None: yield result(previous_user_id) @@ -206,6 +240,8 @@ def result(user_id): module_solves[module_id] = ( int(checkpoint_solves) if checkpoint_solves is not None else 0, int(due_solves) if due_solves is not None else 0, + int(late_solves) if late_solves is not None else 0, + int(extra_late_solves) if extra_late_solves is not None else 0, int(all_solves) if all_solves is not None else 0, ) if user_id: diff --git a/dojo_theme/static/css/custom.css b/dojo_theme/static/css/custom.css index 9fa8679a..f6134e67 100644 --- a/dojo_theme/static/css/custom.css +++ b/dojo_theme/static/css/custom.css @@ -358,8 +358,27 @@ pre { padding: 0px 0px 0px 0px; } + +@media (min-width: 1440px) { + .container { + max-width: 1400px; + } +} +@media (min-width: 1660px) { + .container { + max-width: 1620px; + } +} +@media (min-width: 1980px) { + .container { + max-width: 1940px; + } +} + + + code{ - color: #EE6889; + color: #61afef; } .hljs { diff --git a/dojo_theme/templates/course.html b/dojo_theme/templates/course.html index 7ca057a2..9f185409 100644 --- a/dojo_theme/templates/course.html +++ b/dojo_theme/templates/course.html @@ -63,7 +63,10 @@

{{ name }} current grade in the class: {{ lette Name - Date + Date + {% if show_extra_late_date %} + Extra Late Date + {% endif %} Weight Progress Credit @@ -79,6 +82,9 @@

{{ name }} current grade in the class: {{ lette {{ assessment_grade.name }} {{ assessment_grade.date }} + {% if show_extra_late_date %} + {{ assessment_grade.extra_late_date }} + {% endif %} {{ assessment_grade.weight }} {{ assessment_grade.progress }} {{ credit }} diff --git a/dojo_theme/templates/grades_admin.html b/dojo_theme/templates/grades_admin.html index d9f8c1f0..b3fe917d 100644 --- a/dojo_theme/templates/grades_admin.html +++ b/dojo_theme/templates/grades_admin.html @@ -42,6 +42,9 @@

All Grades

Name Date + {% if user_grades.show_extra_late_date %} + Extra Late After + {% endif %} Weight Progress Credit @@ -52,6 +55,9 @@

All Grades

{{ assessment_grade.name }} {{ assessment_grade.date }} + {% if user_grades.show_extra_late_date %} + {{ assessment_grade.extra_late_date }} + {% endif %} {{ assessment_grade.weight }} {{ assessment_grade.progress }} From 51c2fbd91a1646bb9ec201f8038143b4f7f91be6 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Mon, 5 Aug 2024 14:49:46 -0700 Subject: [PATCH 3/7] removing debugging code --- dojo_plugin/pages/course.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dojo_plugin/pages/course.py b/dojo_plugin/pages/course.py index 5c19c39e..cd28eed4 100644 --- a/dojo_plugin/pages/course.py +++ b/dojo_plugin/pages/course.py @@ -154,10 +154,6 @@ def result(user_id): challenge_count = challenge_counts[module_id] checkpoint_solves, due_solves, late_solves, extra_late_solves, all_solves = module_solves.get(module_id, (0, 0, 0, 0, 0)) - print("@"*50) - print(f"{checkpoint_solves=}, {due_solves=}, {late_solves=}, {extra_late_solves=}") - print("@"*50) - challenge_count_required = int(challenge_count * percent_required) user_date = date + datetime.timedelta(days=extension) extra_late_user_date = None From 10f229fc09c5f8ac3334e62020272b5ceb987596 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Sat, 17 Aug 2024 11:01:03 -0700 Subject: [PATCH 4/7] added some logic for generating student container access tokens and making it easier to launch a container using a GET request --- dojo_plugin/api/v1/docker.py | 44 ++++++++++++------ dojo_plugin/api/v1/workspace_tokens.py | 1 + dojo_plugin/pages/course.py | 62 +++++++++++++++++++++++++- dojo_plugin/utils/__init__.py | 7 ++- 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/dojo_plugin/api/v1/docker.py b/dojo_plugin/api/v1/docker.py index 49c1f68c..6d0b81e1 100644 --- a/dojo_plugin/api/v1/docker.py +++ b/dojo_plugin/api/v1/docker.py @@ -11,6 +11,7 @@ import docker.types from flask import abort, request, current_app from flask_restx import Namespace, Resource +from CTFd.plugins import bypass_csrf_protection from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException from CTFd.utils.user import get_current_user from CTFd.utils.decorators import authed_only @@ -336,20 +337,11 @@ def start_challenge(user, dojo_challenge, practice, *, as_user=None): @docker_namespace.route("") -class RunDocker(Resource): - @authed_only - def post(self): - data = request.get_json() - dojo_id = data.get("dojo") - module_id = data.get("module") - challenge_id = data.get("challenge") - practice = data.get("practice") - user = get_current_user() - as_user = None +class RunDocker(Resource): - # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/initialization/__init__.py#L286-L296 - workspace_token = request.headers.get("X-Workspace-Token") + def launch_container(self, user, dojo_id, module_id, challenge_id, practice, workspace_token): + as_user = None if workspace_token: try: token_user = lookup_workspace_token(workspace_token) @@ -393,9 +385,34 @@ def post(self): logger.exception(f"ERROR: Docker failed for {user.id}:") return {"success": False, "error": "Docker failed"} return {"success": True} + + @authed_only + def post(self): + data = request.get_json() + dojo_id = data.get("dojo") + module_id = data.get("module") + challenge_id = data.get("challenge") + practice = data.get("practice") + user = get_current_user() + as_user = None + + # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/initialization/__init__.py#L286-L296 + workspace_token = request.headers.get("X-Workspace-Token") + + return self.launch_container(user, dojo_id, module_id, challenge_id, practice, workspace_token) + @authed_only - def get(self): + def get(self): + if request.args.get('dojo') and request.args.get('module') and request.args.get('challenge'): + user = get_current_user() + dojo_id = request.args.get("dojo") + module_id = request.args.get("module") + challenge_id = request.args.get("challenge") + workspace_token = request.args.get("X-Workspace-Token", None) + practice = request.args.get("practice", "false").lower() in ('true', '1','yes','y') + return self.launch_container(user, dojo_id, module_id, challenge_id, practice, workspace_token) + dojo_challenge = get_current_dojo_challenge() if not dojo_challenge: return {"success": False, "error": "No active challenge"} @@ -405,3 +422,4 @@ def get(self): "module": dojo_challenge.module.id, "challenge": dojo_challenge.id, } + diff --git a/dojo_plugin/api/v1/workspace_tokens.py b/dojo_plugin/api/v1/workspace_tokens.py index dd917638..36e55c02 100644 --- a/dojo_plugin/api/v1/workspace_tokens.py +++ b/dojo_plugin/api/v1/workspace_tokens.py @@ -1,3 +1,4 @@ +import datetime from flask import request from flask_restx import Namespace, Resource from CTFd.models import ma diff --git a/dojo_plugin/pages/course.py b/dojo_plugin/pages/course.py index cd28eed4..7bb731c5 100644 --- a/dojo_plugin/pages/course.py +++ b/dojo_plugin/pages/course.py @@ -1,6 +1,8 @@ import collections import datetime import re +import logging +from datetime import datetime, timedelta from flask import Blueprint, Response, render_template, request, abort, stream_with_context from sqlalchemy import and_, cast @@ -9,8 +11,10 @@ from CTFd.utils.user import get_current_user, is_admin from CTFd.utils.decorators import authed_only, admins_only, ratelimit -from ..models import DiscordUsers, DojoChallenges, DojoUsers, DojoStudents, DojoModules, DojoStudents -from ..utils import module_visible, module_challenges_visible, is_dojo_admin +from ..api.v1.workspace_tokens import WorkspaceTokenSchema + +from ..models import DiscordUsers, DojoChallenges, DojoUsers, DojoStudents, DojoModules, DojoStudents, WorkspaceTokens +from ..utils import module_visible, module_challenges_visible, is_dojo_admin, generate_workspace_token from ..utils.dojo import dojo_route from ..utils.discord import add_role, get_discord_member from .writeups import WriteupComments, writeup_weeks, all_writeups @@ -471,3 +475,57 @@ def view_user_info(dojo, user_id): user=user, discord_member=discord_member, **identity) + + +@course.route("/dojo//admin/create_tokens") +@dojo_route +@authed_only +def create_tokens_for_course_members(dojo): + if not dojo.course: + abort(404) + + if not dojo.is_admin(): + abort(403) + log = logging.getLogger(__name__) + + course_students = dojo.course.get("students", []) + users = ( + db.session.query(Users,DojoStudents.token) + .join(DojoStudents, DojoStudents.user_id == Users.id) + .filter(DojoStudents.dojo == dojo, + DojoStudents.token.in_(course_students)) + ) + out = [] + import json + for user, user_extra_id in users: + log.info(f"{user.id} {user_extra_id}") + dojo_access_token = None + tokens = WorkspaceTokens.query.filter_by(user_id=user.id) + schema = WorkspaceTokenSchema(only=["id", "expiration","value"], many=True) + response = schema.dump(tokens) + if response.errors: + log.error(f"Error with token getting token info for {user.id}: {user.name}, will attempt to create a new token") + else: + for tok in response.data: + if tok.get('value','').startswith(dojo.id): + dojo_access_token = tok + break + + if dojo_access_token is None: + current_date = datetime.now() + + # TODO: should make more sophisticated, maybe based on a course value like course visibility if it's there, maybe visibility + 30 days or something? + august_10 = datetime(current_date.year, 8, 10) + december_31 = datetime(current_date.year, 12, 31) + if current_date < august_10: + expiration_date = august_10 + else: + expiration_date = december_31 + + newly_generated_token = generate_workspace_token(user, expiration=expiration_date, preface=f"{dojo.id}_ws_") + out.append({"user_id": user.id, "user_extra_id": user_extra_id, "user_name": user.name, + "access_token": newly_generated_token.value, "access_expiration": newly_generated_token.expiration}) + else: + out.append({"user_id": user.id, "user_extra_id": user_extra_id, "user_name": user.name, + "access_token": dojo_access_token.get('value'), "access_expiration": dojo_access_token.get('expiration')}) + return out diff --git a/dojo_plugin/utils/__init__.py b/dojo_plugin/utils/__init__.py index 7a5530c2..5c602cca 100644 --- a/dojo_plugin/utils/__init__.py +++ b/dojo_plugin/utils/__init__.py @@ -296,18 +296,17 @@ def lookup_workspace_token(token): # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/security/auth.py#L37-L48 -def generate_workspace_token(user, expiration=None): +def generate_workspace_token(user, expiration=None, preface="workspace_"): temp_token = True while temp_token is not None: - value = "workspace_" + hexencode(os.urandom(32)) + value = preface + hexencode(os.urandom(32)) temp_token = WorkspaceTokens.query.filter_by(value=value).first() - + token = WorkspaceTokens( user_id=user.id, expiration=expiration, value=value ) db.session.add(token) db.session.commit() - return token # based on https://stackoverflow.com/questions/36408496/python-logging-handler-to-append-to-list From 39dea133cc82cfdcf64b5dd7b3d989f3121f4c34 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Sat, 17 Aug 2024 11:01:03 -0700 Subject: [PATCH 5/7] added some logic for generating student container access tokens and making it easier to launch a container using a GET request --- dojo_plugin/api/v1/docker.py | 44 ++++++++++++------ dojo_plugin/api/v1/workspace_tokens.py | 1 + dojo_plugin/pages/course.py | 62 +++++++++++++++++++++++++- dojo_plugin/utils/__init__.py | 7 ++- 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/dojo_plugin/api/v1/docker.py b/dojo_plugin/api/v1/docker.py index 49c1f68c..6d0b81e1 100644 --- a/dojo_plugin/api/v1/docker.py +++ b/dojo_plugin/api/v1/docker.py @@ -11,6 +11,7 @@ import docker.types from flask import abort, request, current_app from flask_restx import Namespace, Resource +from CTFd.plugins import bypass_csrf_protection from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException from CTFd.utils.user import get_current_user from CTFd.utils.decorators import authed_only @@ -336,20 +337,11 @@ def start_challenge(user, dojo_challenge, practice, *, as_user=None): @docker_namespace.route("") -class RunDocker(Resource): - @authed_only - def post(self): - data = request.get_json() - dojo_id = data.get("dojo") - module_id = data.get("module") - challenge_id = data.get("challenge") - practice = data.get("practice") - user = get_current_user() - as_user = None +class RunDocker(Resource): - # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/initialization/__init__.py#L286-L296 - workspace_token = request.headers.get("X-Workspace-Token") + def launch_container(self, user, dojo_id, module_id, challenge_id, practice, workspace_token): + as_user = None if workspace_token: try: token_user = lookup_workspace_token(workspace_token) @@ -393,9 +385,34 @@ def post(self): logger.exception(f"ERROR: Docker failed for {user.id}:") return {"success": False, "error": "Docker failed"} return {"success": True} + + @authed_only + def post(self): + data = request.get_json() + dojo_id = data.get("dojo") + module_id = data.get("module") + challenge_id = data.get("challenge") + practice = data.get("practice") + user = get_current_user() + as_user = None + + # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/initialization/__init__.py#L286-L296 + workspace_token = request.headers.get("X-Workspace-Token") + + return self.launch_container(user, dojo_id, module_id, challenge_id, practice, workspace_token) + @authed_only - def get(self): + def get(self): + if request.args.get('dojo') and request.args.get('module') and request.args.get('challenge'): + user = get_current_user() + dojo_id = request.args.get("dojo") + module_id = request.args.get("module") + challenge_id = request.args.get("challenge") + workspace_token = request.args.get("X-Workspace-Token", None) + practice = request.args.get("practice", "false").lower() in ('true', '1','yes','y') + return self.launch_container(user, dojo_id, module_id, challenge_id, practice, workspace_token) + dojo_challenge = get_current_dojo_challenge() if not dojo_challenge: return {"success": False, "error": "No active challenge"} @@ -405,3 +422,4 @@ def get(self): "module": dojo_challenge.module.id, "challenge": dojo_challenge.id, } + diff --git a/dojo_plugin/api/v1/workspace_tokens.py b/dojo_plugin/api/v1/workspace_tokens.py index dd917638..36e55c02 100644 --- a/dojo_plugin/api/v1/workspace_tokens.py +++ b/dojo_plugin/api/v1/workspace_tokens.py @@ -1,3 +1,4 @@ +import datetime from flask import request from flask_restx import Namespace, Resource from CTFd.models import ma diff --git a/dojo_plugin/pages/course.py b/dojo_plugin/pages/course.py index cd28eed4..7bb731c5 100644 --- a/dojo_plugin/pages/course.py +++ b/dojo_plugin/pages/course.py @@ -1,6 +1,8 @@ import collections import datetime import re +import logging +from datetime import datetime, timedelta from flask import Blueprint, Response, render_template, request, abort, stream_with_context from sqlalchemy import and_, cast @@ -9,8 +11,10 @@ from CTFd.utils.user import get_current_user, is_admin from CTFd.utils.decorators import authed_only, admins_only, ratelimit -from ..models import DiscordUsers, DojoChallenges, DojoUsers, DojoStudents, DojoModules, DojoStudents -from ..utils import module_visible, module_challenges_visible, is_dojo_admin +from ..api.v1.workspace_tokens import WorkspaceTokenSchema + +from ..models import DiscordUsers, DojoChallenges, DojoUsers, DojoStudents, DojoModules, DojoStudents, WorkspaceTokens +from ..utils import module_visible, module_challenges_visible, is_dojo_admin, generate_workspace_token from ..utils.dojo import dojo_route from ..utils.discord import add_role, get_discord_member from .writeups import WriteupComments, writeup_weeks, all_writeups @@ -471,3 +475,57 @@ def view_user_info(dojo, user_id): user=user, discord_member=discord_member, **identity) + + +@course.route("/dojo//admin/create_tokens") +@dojo_route +@authed_only +def create_tokens_for_course_members(dojo): + if not dojo.course: + abort(404) + + if not dojo.is_admin(): + abort(403) + log = logging.getLogger(__name__) + + course_students = dojo.course.get("students", []) + users = ( + db.session.query(Users,DojoStudents.token) + .join(DojoStudents, DojoStudents.user_id == Users.id) + .filter(DojoStudents.dojo == dojo, + DojoStudents.token.in_(course_students)) + ) + out = [] + import json + for user, user_extra_id in users: + log.info(f"{user.id} {user_extra_id}") + dojo_access_token = None + tokens = WorkspaceTokens.query.filter_by(user_id=user.id) + schema = WorkspaceTokenSchema(only=["id", "expiration","value"], many=True) + response = schema.dump(tokens) + if response.errors: + log.error(f"Error with token getting token info for {user.id}: {user.name}, will attempt to create a new token") + else: + for tok in response.data: + if tok.get('value','').startswith(dojo.id): + dojo_access_token = tok + break + + if dojo_access_token is None: + current_date = datetime.now() + + # TODO: should make more sophisticated, maybe based on a course value like course visibility if it's there, maybe visibility + 30 days or something? + august_10 = datetime(current_date.year, 8, 10) + december_31 = datetime(current_date.year, 12, 31) + if current_date < august_10: + expiration_date = august_10 + else: + expiration_date = december_31 + + newly_generated_token = generate_workspace_token(user, expiration=expiration_date, preface=f"{dojo.id}_ws_") + out.append({"user_id": user.id, "user_extra_id": user_extra_id, "user_name": user.name, + "access_token": newly_generated_token.value, "access_expiration": newly_generated_token.expiration}) + else: + out.append({"user_id": user.id, "user_extra_id": user_extra_id, "user_name": user.name, + "access_token": dojo_access_token.get('value'), "access_expiration": dojo_access_token.get('expiration')}) + return out diff --git a/dojo_plugin/utils/__init__.py b/dojo_plugin/utils/__init__.py index 7a5530c2..5c602cca 100644 --- a/dojo_plugin/utils/__init__.py +++ b/dojo_plugin/utils/__init__.py @@ -296,18 +296,17 @@ def lookup_workspace_token(token): # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/security/auth.py#L37-L48 -def generate_workspace_token(user, expiration=None): +def generate_workspace_token(user, expiration=None, preface="workspace_"): temp_token = True while temp_token is not None: - value = "workspace_" + hexencode(os.urandom(32)) + value = preface + hexencode(os.urandom(32)) temp_token = WorkspaceTokens.query.filter_by(value=value).first() - + token = WorkspaceTokens( user_id=user.id, expiration=expiration, value=value ) db.session.add(token) db.session.commit() - return token # based on https://stackoverflow.com/questions/36408496/python-logging-handler-to-append-to-list From 4b683ff172e0bc100d444fa60fe2e86bf2b7be31 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Sat, 17 Aug 2024 11:24:40 -0700 Subject: [PATCH 6/7] bugfix in course.py introduced with changes --- dojo_plugin/pages/course.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dojo_plugin/pages/course.py b/dojo_plugin/pages/course.py index 7bb731c5..5614aa18 100644 --- a/dojo_plugin/pages/course.py +++ b/dojo_plugin/pages/course.py @@ -2,7 +2,6 @@ import datetime import re import logging -from datetime import datetime, timedelta from flask import Blueprint, Response, render_template, request, abort, stream_with_context from sqlalchemy import and_, cast From e567c4ad68fb9c3616257a275fa59c87961995b4 Mon Sep 17 00:00:00 2001 From: Erik Trickel Date: Sat, 17 Aug 2024 12:12:42 -0700 Subject: [PATCH 7/7] fixed bug with token generator somehow I deleted the return from the generate_token function --- dojo_plugin/api/v1/workspace_tokens.py | 4 ++-- dojo_plugin/utils/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dojo_plugin/api/v1/workspace_tokens.py b/dojo_plugin/api/v1/workspace_tokens.py index 36e55c02..1cf9ceb8 100644 --- a/dojo_plugin/api/v1/workspace_tokens.py +++ b/dojo_plugin/api/v1/workspace_tokens.py @@ -1,4 +1,4 @@ -import datetime +import datetime from flask import request from flask_restx import Namespace, Resource from CTFd.models import ma @@ -52,4 +52,4 @@ def post(self): if response.errors: return {"success": False, "errors": response.errors}, 400 - return {"success": True, "data": response.data} + return {"success": True, "data": response.data} \ No newline at end of file diff --git a/dojo_plugin/utils/__init__.py b/dojo_plugin/utils/__init__.py index 5c602cca..2e98d1a0 100644 --- a/dojo_plugin/utils/__init__.py +++ b/dojo_plugin/utils/__init__.py @@ -297,16 +297,16 @@ def lookup_workspace_token(token): # https://github.com/CTFd/CTFd/blob/3.6.0/CTFd/utils/security/auth.py#L37-L48 def generate_workspace_token(user, expiration=None, preface="workspace_"): - temp_token = True + temp_token = True while temp_token is not None: value = preface + hexencode(os.urandom(32)) temp_token = WorkspaceTokens.query.filter_by(value=value).first() - token = WorkspaceTokens( user_id=user.id, expiration=expiration, value=value ) db.session.add(token) db.session.commit() + return token # based on https://stackoverflow.com/questions/36408496/python-logging-handler-to-append-to-list