Loading aai/access.py +27 −0 Original line number Diff line number Diff line Loading @@ -153,3 +153,30 @@ def definition_access(context: Request, definition_id: int): raise PermissionDenied( f"User does not have access to this definition ({definition_id})" ) def token_from_context(context: Request) -> Optional[str]: meta = getattr(context, "META", {}) token = meta.get("HTTP_TEAM_TOKEN") return token def token_team_access(context: Request, team_id: int) -> ExerciseAccess: token = token_from_context(context) if not token: raise AuthenticationFailed("Authentication failed") access = ( ExerciseAccess.objects.select_related("user", "exercise") .filter(token=token, team_ids__contains=[team_id]) .first() ) if access is None: raise PermissionDenied( f"User does not have access to this team ({team_id})" ) add_access_entry(context, access) return access common_lib/schema/types.py +19 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ from running_exercise.models import ( ActionLog, EmailThread, Email, SandboxLogDetails, ThreadParticipant, ToolDetails, InjectDetails, Loading Loading @@ -999,6 +1000,16 @@ class IMilestoneModificationDetailsType(DjangoObjectType): ) class TSandboxLogDetailsType(DjangoObjectType): class Meta: model = SandboxLogDetails class ISandboxLogDetailsType(DjangoObjectType): class Meta: model = SandboxLogDetails class TActionLogDetails(graphene.Union): class Meta: types = [ Loading @@ -1012,6 +1023,7 @@ class TActionLogDetails(graphene.Union): TTeamQuestionnaireStateType, TFileDownloadDetailsType, TMilestoneModificationDetailsType, TSandboxLogDetailsType, ] Loading Loading @@ -1047,6 +1059,7 @@ class IActionLogDetails(graphene.Union): ITeamQuestionnaireStateType, IFileDownloadDetailsType, IMilestoneModificationDetailsType, ISandboxLogDetailsType, ] Loading Loading @@ -1417,6 +1430,12 @@ class NoticeboardMessageType(DjangoObjectType): created_by = graphene.Field(TUserType, required=False) class TeamToken(graphene.ObjectType): token = graphene.String(required=True) team_ids = graphene.List(graphene.NonNull(graphene.ID), required=True) user = graphene.Field(TUserType, required=True) class AssignmentResultType(graphene.ObjectType): assigned_user_ids = graphene.List( graphene.NonNull(graphene.ID), required=True Loading exercise/graphql_inputs.py +1 −1 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ class CreateExerciseBase: team_count = graphene.Int(required=True) name = graphene.String(required=False, default_value="") on_demand = graphene.Boolean(required=False, default_value=False) technical = graphene.Boolean(required=False, default_value=False) sandbox = graphene.Boolean(required=False, default_value=False) class CreateExerciseType(CreateExerciseBase, graphene.ObjectType): Loading exercise/lib/exercise_manager.py +2 −8 Original line number Diff line number Diff line Loading @@ -139,12 +139,6 @@ def validate_input( "Start interval cannot be specified for not on-demand exercises" ) if create_exercise_input.technical: if not settings.OPENSEARCH_HOST: return Err( "Cannot create technical exercise, OPENSEARCH_HOST is not set" ) return None Loading Loading @@ -210,7 +204,7 @@ def create_exercise( config=config, created_by_id=creator.id, on_demand=create_exercise_input.on_demand, technical=create_exercise_input.technical, sandbox=create_exercise_input.sandbox, ) if len(exercise.name) == 0: exercise.name = f"Exercise {exercise.id}" Loading @@ -226,7 +220,7 @@ def create_exercise( create_team_questionnaire_states(teams, definition) create_drive_permissions(teams, definition.files.filter(is_drive=True)) if exercise.technical: if exercise.sandbox and settings.OPENSEARCH_HOST: create_opensearch_exercise(teams, exercise.id) return exercise Loading exercise/migrations/0022_auto_20251123_2020.py 0 → 100644 +23 −0 Original line number Diff line number Diff line # Generated by Django 3.2.25 on 2025-11-23 20:20 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('exercise', '0021_auto_20251027_1107'), ] operations = [ migrations.RenameField( model_name='exercise', old_name='technical', new_name='sandbox', ), migrations.AddField( model_name='exerciseaccess', name='token', field=models.TextField(default=''), ), ] Loading
aai/access.py +27 −0 Original line number Diff line number Diff line Loading @@ -153,3 +153,30 @@ def definition_access(context: Request, definition_id: int): raise PermissionDenied( f"User does not have access to this definition ({definition_id})" ) def token_from_context(context: Request) -> Optional[str]: meta = getattr(context, "META", {}) token = meta.get("HTTP_TEAM_TOKEN") return token def token_team_access(context: Request, team_id: int) -> ExerciseAccess: token = token_from_context(context) if not token: raise AuthenticationFailed("Authentication failed") access = ( ExerciseAccess.objects.select_related("user", "exercise") .filter(token=token, team_ids__contains=[team_id]) .first() ) if access is None: raise PermissionDenied( f"User does not have access to this team ({team_id})" ) add_access_entry(context, access) return access
common_lib/schema/types.py +19 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ from running_exercise.models import ( ActionLog, EmailThread, Email, SandboxLogDetails, ThreadParticipant, ToolDetails, InjectDetails, Loading Loading @@ -999,6 +1000,16 @@ class IMilestoneModificationDetailsType(DjangoObjectType): ) class TSandboxLogDetailsType(DjangoObjectType): class Meta: model = SandboxLogDetails class ISandboxLogDetailsType(DjangoObjectType): class Meta: model = SandboxLogDetails class TActionLogDetails(graphene.Union): class Meta: types = [ Loading @@ -1012,6 +1023,7 @@ class TActionLogDetails(graphene.Union): TTeamQuestionnaireStateType, TFileDownloadDetailsType, TMilestoneModificationDetailsType, TSandboxLogDetailsType, ] Loading Loading @@ -1047,6 +1059,7 @@ class IActionLogDetails(graphene.Union): ITeamQuestionnaireStateType, IFileDownloadDetailsType, IMilestoneModificationDetailsType, ISandboxLogDetailsType, ] Loading Loading @@ -1417,6 +1430,12 @@ class NoticeboardMessageType(DjangoObjectType): created_by = graphene.Field(TUserType, required=False) class TeamToken(graphene.ObjectType): token = graphene.String(required=True) team_ids = graphene.List(graphene.NonNull(graphene.ID), required=True) user = graphene.Field(TUserType, required=True) class AssignmentResultType(graphene.ObjectType): assigned_user_ids = graphene.List( graphene.NonNull(graphene.ID), required=True Loading
exercise/graphql_inputs.py +1 −1 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ class CreateExerciseBase: team_count = graphene.Int(required=True) name = graphene.String(required=False, default_value="") on_demand = graphene.Boolean(required=False, default_value=False) technical = graphene.Boolean(required=False, default_value=False) sandbox = graphene.Boolean(required=False, default_value=False) class CreateExerciseType(CreateExerciseBase, graphene.ObjectType): Loading
exercise/lib/exercise_manager.py +2 −8 Original line number Diff line number Diff line Loading @@ -139,12 +139,6 @@ def validate_input( "Start interval cannot be specified for not on-demand exercises" ) if create_exercise_input.technical: if not settings.OPENSEARCH_HOST: return Err( "Cannot create technical exercise, OPENSEARCH_HOST is not set" ) return None Loading Loading @@ -210,7 +204,7 @@ def create_exercise( config=config, created_by_id=creator.id, on_demand=create_exercise_input.on_demand, technical=create_exercise_input.technical, sandbox=create_exercise_input.sandbox, ) if len(exercise.name) == 0: exercise.name = f"Exercise {exercise.id}" Loading @@ -226,7 +220,7 @@ def create_exercise( create_team_questionnaire_states(teams, definition) create_drive_permissions(teams, definition.files.filter(is_drive=True)) if exercise.technical: if exercise.sandbox and settings.OPENSEARCH_HOST: create_opensearch_exercise(teams, exercise.id) return exercise Loading
exercise/migrations/0022_auto_20251123_2020.py 0 → 100644 +23 −0 Original line number Diff line number Diff line # Generated by Django 3.2.25 on 2025-11-23 20:20 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('exercise', '0021_auto_20251027_1107'), ] operations = [ migrations.RenameField( model_name='exercise', old_name='technical', new_name='sandbox', ), migrations.AddField( model_name='exerciseaccess', name='token', field=models.TextField(default=''), ), ]