diff --git a/README.md b/README.md index 4e32164..e693fc6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Parable allows a competitor end user to view a wide variety of information relev Enigma has Discord integration and will automatically manage channels and roles. Competitors will be able to submit various requests through Discord to supplement their use of Parable, such as green team support and box reset requests. ## Details -Built on Python 3.13.0 with Django and FastAPI +Built on Python 3.13.0 with Flask Highly extensible with a common framework for custom service checks @@ -27,4 +27,4 @@ The following table lists the ports of each service: |PostgreSQL|5432| |Nginx|80,443| -Nginx should be the only service exposed +Nginx should be the only service exposed \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index dde929e..0dd97fa 100644 --- a/compose.yaml +++ b/compose.yaml @@ -26,11 +26,12 @@ services: retries: 3 start_interval: 2s - parable: container_name: parable image: parable:latest - build: ./parable + build: + context: . + dockerfile: ./docker/parable/Dockerfile ports: - "5070:5070" restart: no diff --git a/db_models/__init__.py b/db_models/__init__.py deleted file mode 100644 index af47230..0000000 --- a/db_models/__init__.py +++ /dev/null @@ -1,96 +0,0 @@ -from sqlmodel import SQLModel, Field - -# Box -class BoxDB(SQLModel, table = True): - __tablename__ = 'boxes' - - name: str = Field(primary_key=True) - identifier: int = Field(ge=1, le=255, unique=True) - service_config: str - -# Credlist -class CredlistDB(SQLModel, table=True): - __tablename__ = 'credlists' - - name: str = Field(primary_key=True) - creds: str - -# TeamCreds -class TeamCredsDB(SQLModel, table=True): - __tablename__ = 'teamcreds' - - name: str = Field(foreign_key='credlists.name', primary_key=True) - team_id: int = Field(foreign_key='teams.identifier', primary_key=True) - creds: str - -# Inject -class InjectDB(SQLModel, table=True): - __tablename__ = 'injects' - - id: int = Field(primary_key=True) - name: str = Field(unique=True) - desc: str - worth: int - path: str | None = None - rubric: str - -# InjectReport -class InjectReportDB(SQLModel, table=True): - __tablename__ = 'injectreports' - - team_id: int = Field(foreign_key='teams.identifier', primary_key=True) - inject_num: int = Field(foreign_key='injects.id', primary_key=True) - score: int - breakdown: str - -# Score reports -class ScoreReportDB(SQLModel, table=True): - __tablename__ = 'scorereports' - - team_id: int = Field(foreign_key='teams.identifier', primary_key=True) - round: int = Field(primary_key=True) - score: int - msg: str - -# SLA Report -class SLAReportDB(SQLModel, table=True): - __tablename__ = 'slareports' - - team_id: int = Field(foreign_key='teams.identifier', primary_key=True) - round: int = Field(primary_key=True) - service: str = Field(primary_key=True) - -# Team -class RvBTeamDB(SQLModel, table=True): - __tablename__ = 'teams' - - name: str = Field(primary_key=True, foreign_key='parableusers.name') - identifier: int = Field(ge=1, le=255, unique=True) - score: int - -# ParableUser -class ParableUserDB(SQLModel, table=True): - __tablename__ = 'parableusers' - - name: str = Field(primary_key=True) - identifier: int = Field(ge=1, le=255, unique=True) - permission_level: int = Field(ge=0, le=2) - pw_hash: bytes | None = Field(default=None) - -# Settings -class SettingsDB(SQLModel, table=True): - __tablename__ = 'settings' - - id: int | None = Field(default=None, primary_key=True) - competitor_info: str = Field(default='minimal') - pcr_portal: bool = Field(default=True) - inject_portal: bool = Field(default=True) - comp_name: str = Field(default='example') - check_time: int = Field(default=30) - check_jitter: int = Field(default=0, ge=0) - check_timeout: int = Field(default=5, ge=5) - check_points: int = Field(default=10, ge=1) - sla_requirement: int = Field(default=5, ge=1) - sla_penalty: int = Field(default=100, ge=0) - first_octets: str = Field(default='10.10') - first_pod_third_octet: int = Field(default=1, ge=1, le=255) \ No newline at end of file diff --git a/docker/enigma/Dockerfile b/docker/enigma/Dockerfile index 882c128..487ac45 100644 --- a/docker/enigma/Dockerfile +++ b/docker/enigma/Dockerfile @@ -4,14 +4,14 @@ FROM python:3.13-alpine WORKDIR /app # Install requirements -COPY /enigma/requirements.txt /app/requirements.txt +COPY /requirements/enigma/requirements.txt /app/requirements.txt RUN python -m pip install --no-cache-dir --upgrade pip RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt # Copy modules COPY /enigma /app/enigma -COPY /db_models /app/db_models +COPY /enigma_models /app/enigma_models # Copy main.py COPY /main/enigma/main.py /app/main.py diff --git a/docker/parable/Dockerfile b/docker/parable/Dockerfile index 33b56a2..3914408 100644 --- a/docker/parable/Dockerfile +++ b/docker/parable/Dockerfile @@ -1,4 +1,20 @@ # syntax=docker/dockerfile:1 FROM python:3.13-alpine -WORKDIR /app \ No newline at end of file +WORKDIR /app + +# Install requirements +COPY --from=ghcr.io/astral-sh/uv:0.5.31 /uv /uvx /bin/ + +COPY /requirements/parable/requirements.txt /app/requirements.txt +RUN uv pip install --system -r /app/requirements.txt + +# Copy modules +COPY /parable /app/parable +COPY /enigma_models /app/enigma_models + +# Copy main.py +COPY /main/parable/main.py /app/main.py + +# Run main.py +CMD ["python", "main.py"] \ No newline at end of file diff --git a/docker/praxos/Dockerfile b/docker/praxos/Dockerfile index 69abb46..1b84c75 100644 --- a/docker/praxos/Dockerfile +++ b/docker/praxos/Dockerfile @@ -11,7 +11,7 @@ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt # Copy modules COPY /praxos /app/praxos -COPY /db_models /app/db_models +COPY /enigma_models /app/enigma_models # Copy main.py COPY /main/praxos/main.py /app/main.py diff --git a/enigma/checks/__init__.py b/enigma/checks/__init__.py index f56dcae..34210e2 100644 --- a/enigma/checks/__init__.py +++ b/enigma/checks/__init__.py @@ -30,7 +30,7 @@ def __eq__(self, obj): # This method is perhaps most important. It conducts a service check and returns a boolean to represent the result # Note that conduct_service_check() will be called in a worker process, not the main thread # Implementations of conduct_service_check() must check kwargs for check info - # This is used to properly target a team's box + # This is used to properly target a competitor's box # e.x. If the pod networks are on 172.16..0, then conduct_service_check() will target 172.16.. # e.x. If Team01 has identifier '32', and an SSHService is configured on Box 'examplebox' with host octet 5, # then the worker process will target 172.16.32.5 diff --git a/enigma/engine/__init__.py b/enigma/engine/__init__.py index d04a27f..eeeb312 100644 --- a/enigma/engine/__init__.py +++ b/enigma/engine/__init__.py @@ -5,11 +5,18 @@ load_dotenv(override=True) postgres_settings = { - 'user': getenv('POSTGRES_USER'), + 'competitor': getenv('POSTGRES_USER'), 'password': getenv('POSTGRES_PASSWORD'), 'host': getenv('POSTGRES_HOST'), 'port': getenv('POSTGRES_PORT') } +rabbitmq_settings = { + 'competitor': getenv('RABBITMQ_DEFAULT_USER'), + 'password': getenv('RABBITMQ_DEFAULT_PASSWORD'), + 'host': getenv('RABBITMQ_HOST'), + 'port': 5672 +} + static_path = join(getcwd(), 'static') checks_path = join(getcwd(), 'enigma') \ No newline at end of file diff --git a/enigma/engine/cmd.py b/enigma/engine/cmd.py index c58c253..0245546 100644 --- a/enigma/engine/cmd.py +++ b/enigma/engine/cmd.py @@ -4,7 +4,7 @@ from enigma.logger import log from enigma.broker import RabbitMQ -from enigma.engine.scoring import ScoringEngine, RvBScoringEngine +from enigma.engine.scoring import RvBScoringEngine # TODO: Add event scheduler to create box score start times class RvBCMD: @@ -61,7 +61,7 @@ def decode_cmd(self, cmd): case 'set_rounds': if isinstance(self.engine, RvBScoringEngine): if not self.engine.engine_lock: - self.rounds = cmd_args[1] + self.rounds = int(cmd_args[1]) log.info(f'Setting rounds to {self.rounds}') else: log.warning('Cannot change number of rounds while running!') @@ -97,4 +97,6 @@ def decode_cmd(self, cmd): else: log.warning('Enigma is not running!') else: - log.error('Engine does not exist!') \ No newline at end of file + log.error('Engine does not exist!') + case _: + log.error('Unknown command!') \ No newline at end of file diff --git a/enigma/engine/database.py b/enigma/engine/database.py deleted file mode 100644 index cf276a5..0000000 --- a/enigma/engine/database.py +++ /dev/null @@ -1,38 +0,0 @@ -from sqlmodel import create_engine, SQLModel, Session - -from enigma.engine import postgres_settings - -db_engine = create_engine( - f'postgresql+psycopg://{postgres_settings['user']}:{postgres_settings['password']}@{postgres_settings['host']}:{postgres_settings['port']}/enigma', - echo=False -) - -def init_db(): - from db_models import ( - BoxDB, - CredlistDB, - TeamCredsDB, - InjectDB, - InjectReportDB, - ScoreReportDB, - SLAReportDB, - RvBTeamDB, - ParableUserDB, - SettingsDB - ) - SQLModel.metadata.create_all(db_engine) - -def del_db(): - from db_models import ( - BoxDB, - CredlistDB, - TeamCredsDB, - InjectDB, - InjectReportDB, - ScoreReportDB, - SLAReportDB, - RvBTeamDB, - ParableUserDB, - SettingsDB - ) - SQLModel.metadata.drop_all(db_engine) \ No newline at end of file diff --git a/enigma/engine/rabbitmq.py b/enigma/engine/rabbitmq.py index 00ddcf9..2795795 100644 --- a/enigma/engine/rabbitmq.py +++ b/enigma/engine/rabbitmq.py @@ -5,7 +5,7 @@ class RabbitMQ: def __init__(self): - self.user = rabbitmq_settings['user'] + self.user = rabbitmq_settings['competitor'] self.password = rabbitmq_settings['password'] self.host = rabbitmq_settings['host'] self.port = rabbitmq_settings['port'] diff --git a/enigma/engine/scoring.py b/enigma/engine/scoring.py index fc2c139..3b90038 100644 --- a/enigma/engine/scoring.py +++ b/enigma/engine/scoring.py @@ -12,9 +12,9 @@ from enigma.broker import RabbitMQ from enigma.models.box import Box -from enigma.models.credlist import Credlist +from enigma_models.models.credlist import Credlist from enigma.models.team import RvBTeam -from enigma.models.settings import Settings +from enigma_models.models.settings import Settings class ScoringEngine: @@ -90,9 +90,9 @@ def run(self, total_rounds: int = 0): self.engine_lock = False # Exporting end-of-scoring data - log.info('Exporting individual team score breakdowns') + log.info('Exporting individual competitor score breakdowns') for team in self.teams: - team.export_breakdowns(f'team{team.identifier}_final', static_path) + team.export_breakdowns(f'competitor{team.identifier}_final', static_path) # Score check methods @@ -135,7 +135,7 @@ def score_services(self): log.debug('Created score checks with check data') # Presumed guilty check results - # reports = {team identifier: {service: [result, msg]}} + # reports = {competitor identifier: {service: [result, msg]}} reports = {} for team in self.teams: team_results = {} @@ -154,7 +154,7 @@ def score_services(self): reports[result[0]][result[1]][1] = result[2] log.debug('Scores updated, proceeding to tabulate scores') - # Tabulate scores for each team + # Tabulate scores for each competitor for team in self.teams: team.tabulate_scores(self.round, reports[team.identifier]) @@ -162,7 +162,7 @@ def score_services(self): # Finds and applies check options for a service def get_check_options(self, service: Service, team: RvBTeam): - log.debug(f'Creating check data for team {team.identifier} with service {service.name}') + log.debug(f'Creating check data for competitor {team.identifier} with service {service.name}') check_options = [] # If check requires a credlist, get a random cred and add it to options @@ -269,7 +269,7 @@ def update_comp(self): log.info("RvB competition environment loaded") log.info("Searching for RvB teams") - self.teams = RvBTeam.find_all(self.services) + self.teams = RvBTeam.find_all(services=self.services) if len(self.teams) == 0: self.teams_detected = False @@ -277,7 +277,7 @@ def update_comp(self): return else: self.teams_detected = True - log.info("RvB teams found, creating team credlists") + log.info("RvB teams found, creating competitor credlists") for team in self.teams: team.create_credlists( self.credlists diff --git a/enigma/logger.py b/enigma/logger.py index 5ca62a5..132d53a 100644 --- a/enigma/logger.py +++ b/enigma/logger.py @@ -1,4 +1,5 @@ import logging +from logging import FileHandler from os import getenv, getcwd from os.path import join @@ -32,7 +33,7 @@ def write_log_header(): ) # Handlers for file and stream output -file_handler = logging.FileHandler( +file_handler = FileHandler( log_file, mode = 'a', encoding = 'utf-8' diff --git a/enigma/models/box.py b/enigma/models/box.py index 4a73911..a138bf9 100644 --- a/enigma/models/box.py +++ b/enigma/models/box.py @@ -5,18 +5,15 @@ from enigma.checks import Service from enigma import possible_services -from enigma.engine.database import db_engine from enigma.logger import log -from db_models import BoxDB +from enigma_models.models.box import Box as BoxModel # Box -class Box: +class Box(BoxModel): def __init__(self, name: str, identifier: int, service_config: dict): - self.name = name - self.identifier = identifier - self.service_config = service_config + super().__init__(name, identifier, service_config) self.services = self.compile_services() log.debug(f"Created new Box with name {self.name}") @@ -42,61 +39,26 @@ def compile_services(self) -> list[Service]: return services - ####################### - # DB fetch/add - - # Tries to add the box object to the DB. If exists, it will return False, else True - def add_to_db(self) -> bool: - log.debug(f"Adding Box {self.name} to database") - try: - with Session(db_engine) as session: - session.add( - BoxDB( - name=self.name, - identifier=self.identifier, - service_config=json.dumps(self.service_config) - ) - ) - session.commit() - return True - except: - log.warning(f"Failed to add Box {self.name} to database!") - return False - - # Fetches all Box from the DB - @classmethod - def find_all(cls): - log.debug(f"Retrieving all boxes from database") - boxes = [] - with Session(db_engine) as session: - db_boxes = session.exec(select(BoxDB)).all() - for box in db_boxes: - boxes.append( - Box.new( - name=box.name, - identifier=box.identifier, - data=box.service_config - ) - ) - return boxes - # Gets the names of all the services @classmethod def all_service_names(cls, boxes: list): - log.debug("Finding formatted service names for all boxes") services = [] for box in boxes: services.extend( box.get_service_names() ) return services - - # Creates a new Box object based off of DB data + @classmethod - def new(cls, name: str, identifier: int, data: str): - log.debug(f"Creating new Box {name} with identifier {identifier}") - return cls( - name=name, - identifier=identifier, - service_config=json.loads(data) - ) \ No newline at end of file + def find_all(cls): + boxes = [] + db_boxes = super().find_all() + for db_box in db_boxes: + boxes.append( + Box( + name=db_box.name, + identifier=db_box.identifier, + service_config=db_box.service_config + ) + ) + return boxes \ No newline at end of file diff --git a/enigma/models/inject.py b/enigma/models/inject.py index 1132eb9..b607e0a 100644 --- a/enigma/models/inject.py +++ b/enigma/models/inject.py @@ -1,22 +1,12 @@ -import json - -from sqlmodel import Session, select - -from enigma.engine.database import db_engine from enigma.logger import log -from db_models import InjectDB, InjectReportDB +from enigma_models.models.inject import Inject as InjectModel # Inject -class Inject: +class Inject(InjectModel): def __init__(self, id: int, name: str, desc: str, worth: int, path: str | None, rubric: dict): - self.id = id - self.name = name - self.desc = desc - self.worth = worth - self.path = path - self.rubric = rubric + super().__init__(id, name, desc, worth, path, rubric) self.breakdown = self.calculate_score_breakdown() log.debug(f"Created new Inject with name {self.name}") @@ -38,100 +28,4 @@ def calculate_score_breakdown(self): breakdown.update({ key: possible_cat_scores }) - return breakdown - - ####################### - # DB fetch/add - - # Tries to add the inject object to the DB. If exists, it will return False, else True - def add_to_db(self): - log.debug(f"Adding Inject {self.name} to database") - try: - with Session(db_engine) as session: - session.add( - InjectDB( - id=self.id, - name=self.name, - desc=self.desc, - worth=self.worth, - path=self.path, - rubric=json.dumps(self.rubric) - ) - ) - session.commit() - return True - except: - log.warning(f"Failed to add Inject {self.name} to database!") - return False - - # Fetches all Inject from the DB - @classmethod - def find_all(cls): - log.debug(f"Finding all Injects") - injects = [] - with Session(db_engine) as session: - db_injects = session.exec(select(InjectDB)).all() - for inject in db_injects: - injects.append( - Inject.new( - id=inject.id, - name=inject.name, - desc=inject.desc, - worth=inject.worth, - path=inject.path, - rubric=json.loads(inject.rubric) - ) - ) - return injects - - # Creates an Inject object based off of DB data - @classmethod - def new(cls, id: int, name: str, desc: str, worth: int, path: str | None, rubric: str): - log.debug(f"Creating new Inject {name}") - return cls( - id=id, - name=name, - desc=desc, - worth=worth, - path=path, - rubric=json.loads(rubric) - ) - -# Inject reports -class InjectReport: - def __init__(self, team_id: int, inject_num: int, score: int, breakdown: str): - self.team_id = team_id - self.inject_num = inject_num - self.score = score - self.breakdown = breakdown - - ####################### - # DB fetch/add - - @classmethod - def get_report(cls, team_id: int, inject_num: int) -> tuple[int, dict]: - log.debug(f"Finding InjectReport for team {team_id} with inject number {inject_num}") - with Session(db_engine) as session: - db_report = session.exec( - select( - InjectReportDB - ).where( - InjectReportDB.team_id == team_id - ).where( - InjectReportDB.inject_num == inject_num - ) - ).one() - return (db_report.score, json.loads(db_report.breakdown)) - - @classmethod - def get_all_team_reports(cls, team_id: int)-> list[tuple[int, int]]: - log.debug(f"Finding all InjectReport for team {team_id}") - with Session(db_engine) as session: - db_reports = session.exec( - select( - InjectReportDB - ).where( - InjectReportDB.team_id == team_id - ) - ).all() - return [(db_report.inject_num, db_report.score) for db_report in db_reports] \ No newline at end of file + return breakdown \ No newline at end of file diff --git a/enigma/models/scorereport.py b/enigma/models/scorereport.py deleted file mode 100644 index 938498a..0000000 --- a/enigma/models/scorereport.py +++ /dev/null @@ -1,30 +0,0 @@ -from sqlmodel import Session - -from enigma.logger import log -from enigma.engine.database import db_engine - -from db_models import ScoreReportDB - -# Score reports -class ScoreReport: - def __init__(self, team_id: int, round: int, score: int, msg: str): - self.team_id = team_id - self.round = round - self.score = score - self.msg = msg - - ####################### - # DB fetch/add - - def add_to_db(self): - log.debug(f'Adding score report to database for team {self.team_id} during round {self.round}') - with Session(db_engine) as session: - session.add( - ScoreReportDB( - team_id=self.team_id, - round=self.round, - score=self.score, - msg=self.msg - ) - ) - session.commit() \ No newline at end of file diff --git a/enigma/models/team.py b/enigma/models/team.py index 5df11a8..f57d33c 100644 --- a/enigma/models/team.py +++ b/enigma/models/team.py @@ -5,21 +5,21 @@ from sqlmodel import Session, select from enigma.logger import log -from enigma.engine.database import db_engine -from enigma.models.credlist import Credlist, TeamCreds -from enigma.models.settings import Settings -from enigma.models.slareport import SLAReport -from enigma.models.scorereport import ScoreReport -from enigma.models.inject import InjectReport +from enigma_models.models.credlist import Credlist, TeamCreds +from enigma_models.models.settings import Settings +from enigma_models.models.slareport import SLAReport +from enigma_models.models.scorereport import ScoreReport +from enigma_models.models.inject import InjectReport -from db_models import RvBTeamDB, ParableUserDB +from enigma_models.models.team import RvBTeam as RvBTeamModel +from enigma_models.models.team import RvBTeamDB +from enigma_models.database import db_engine # Team -class RvBTeam: +class RvBTeam(RvBTeamModel): def __init__(self, name: str, identifier: int, services: list[str]): - self.name = name - self.identifier = identifier + super().__init__(name, identifier, 0) self.total_scores = { 'total_score': 0, 'raw_score': 0, @@ -31,7 +31,7 @@ def __init__(self, name: str, identifier: int, services: list[str]): log.debug(f'Created RvBTeam {self.name}') def __repr__(self): - return '<{}> with team id {} and total score {}'.format( + return '<{}> with competitor id {} and total score {}'.format( type(self).__name__, self.identifier, self.total_scores['total_score'] @@ -97,7 +97,7 @@ def tabulate_scores(self, round: int, reports: dict[str: list[bool, str]]): score=self.total_scores['total_score'], msg=json.dumps(msgs) ).add_to_db() - log.debug(f'Published score report for team {self.name}') + log.debug(f'Published score report for competitor {self.name}') # Updates total score def update_total(self): @@ -112,6 +112,7 @@ def update_total(self): self.total_scores['penalty_score'] = total self.total_scores['total_score'] = self.total_scores['raw_score'] - self.total_scores['penalty_score'] + self.score = self.total_scores['total_score'] self.update_in_db() # Service adding/removal @@ -215,17 +216,17 @@ def export_scores_csv(self, name_fmt: str, path: str): ####################### # Creds methods - # Creates copies of the credlists specific to the team + # Creates copies of the credlists specific to the competitor def create_credlists(self, credlists: list[Credlist]): log.debug(f'Creating credlists for {self.name}') for credlist in credlists: TeamCreds( name=credlist.name, team_id=self.identifier, - creds=json.dumps(credlist.creds) + creds=credlist.creds ).add_to_db() - # Returns a random user and password for use in service check + # Returns a random competitor and password for use in service check # Parameter credlists is a list of names of the credlists to choose from def get_random_cred(self, credlists: list[str]) -> dict: log.debug(f'Getting random cred for {self.name}') @@ -240,51 +241,11 @@ def get_random_cred(self, credlists: list[str]) -> dict: return choice ####################### - # DB fetch/add - - # Tries to add the team object to the DB. If exists, it will return False, else True - def add_to_db(self): - log.debug(f'Adding Team {self.name} to database') - try: - with Session(db_engine) as session: - session.add( - ParableUserDB( - name=self.name, - identifier=self.identifier, - permission_level=2 - ) - ) - - session.add( - RvBTeamDB( - name=self.name, - identifier=self.identifier, - score=self.total_scores['total_score'] - ) - ) - session.commit() - return True - except: - log.warning(f'Failed to add Team {self.name} to database!') - return False - - # Updates score in DB - def update_in_db(self): - log.debug(f'Updating score for Team {self.name} in database') - with Session(db_engine) as session: - session.exec( - select( - RvBTeamDB - ).where( - RvBTeamDB.identifier == self.identifier - ) - ).one().score = self.total_scores['total_score'] - session.commit() + # DB # Fetches all Team from the DB @classmethod - def find_all(cls, services: list[str]): - log.debug(f'Retrieving all teams from database') + def find_all(cls, **kwargs): teams = [] with Session(db_engine) as session: db_teams = session.exec( @@ -297,17 +258,7 @@ def find_all(cls, services: list[str]): RvBTeam( name=db_team.name, identifier=db_team.identifier, - services=services + services=kwargs['services'] ) ) - return teams - - # Creates a new Team from the config info - @classmethod - def new(cls, name: str, identifier: int, services: list[str]): - log.debug(f'Creating new Team {name}') - return cls( - name=name, - identifier=identifier, - services=services - ) \ No newline at end of file + return teams \ No newline at end of file diff --git a/enigma_models/__init__.py b/enigma_models/__init__.py new file mode 100644 index 0000000..4d1de2b --- /dev/null +++ b/enigma_models/__init__.py @@ -0,0 +1,11 @@ +from os import getenv +from dotenv import load_dotenv + +load_dotenv(override=True) + +postgres_settings = { + 'user': getenv('POSTGRES_USER'), + 'password': getenv('POSTGRES_PASSWORD'), + 'host': getenv('POSTGRES_HOST'), + 'port': getenv('POSTGRES_PORT') +} \ No newline at end of file diff --git a/db_models/auth.py b/enigma_models/auth.py similarity index 60% rename from db_models/auth.py rename to enigma_models/auth.py index 5fdb464..15b933f 100644 --- a/db_models/auth.py +++ b/enigma_models/auth.py @@ -1,19 +1,19 @@ from hashlib import scrypt from os import urandom -def get_hash(to_hash: str) -> bytes: +def get_hash(to_hash: str | bytes) -> bytes: salt = urandom(32) return scrypt( - to_hash.encode('utf-8'), + to_hash.encode('utf-8') if type(to_hash) == str else to_hash, salt=salt, n=16384, r=8, p=1 ) + salt -def get_hash_from_salt(to_hash: str, salt: bytes) -> bytes: +def get_hash_from_salt(to_hash: str | bytes, salt: bytes) -> bytes: return scrypt( - to_hash.encode('utf-8'), + to_hash.encode('utf-8') if type(to_hash) == str else to_hash, salt=salt, n=16384, r=8, @@ -23,6 +23,6 @@ def get_hash_from_salt(to_hash: str, salt: bytes) -> bytes: def get_hash_from_salted_hash(from_hash: bytes) -> tuple: return from_hash[:-32], from_hash[-32:] -def verify_hash(plain_pw: str, hashed_pw: bytes) -> bool: +def verify_hash(plain_pw: str | bytes, hashed_pw: bytes) -> bool: split_hash = get_hash_from_salted_hash(hashed_pw) return get_hash_from_salt(plain_pw, split_hash[1]) == split_hash[0] \ No newline at end of file diff --git a/enigma_models/database.py b/enigma_models/database.py new file mode 100644 index 0000000..b27ad93 --- /dev/null +++ b/enigma_models/database.py @@ -0,0 +1,32 @@ +from sqlmodel import create_engine, SQLModel, Session + +from enigma_models import postgres_settings + +db_engine = create_engine( + f'postgresql+psycopg://{postgres_settings['user']}:{postgres_settings['password']}@{postgres_settings['host']}:{postgres_settings['port']}/enigma', + echo=False +) + +def init_db(): + from enigma_models.models.box import BoxDB + from enigma_models.models.credlist import CredlistDB, TeamCredsDB + from enigma_models.models.inject import InjectDB, InjectReportDB + from enigma_models.models.scorereport import ScoreReportDB + from enigma_models.models.settings import SettingsDB + from enigma_models.models.slareport import SLAReportDB + from enigma_models.models.team import RvBTeamDB + from enigma_models.models.user import ParableUserDB + + SQLModel.metadata.create_all(db_engine) + +def del_db(): + from enigma_models.models.box import BoxDB + from enigma_models.models.credlist import CredlistDB, TeamCredsDB + from enigma_models.models.inject import InjectDB, InjectReportDB + from enigma_models.models.scorereport import ScoreReportDB + from enigma_models.models.settings import SettingsDB + from enigma_models.models.slareport import SLAReportDB + from enigma_models.models.team import RvBTeamDB + from enigma_models.models.user import ParableUserDB + + SQLModel.metadata.drop_all(db_engine) \ No newline at end of file diff --git a/parable/models/__init__.py b/enigma_models/models/__init__.py similarity index 100% rename from parable/models/__init__.py rename to enigma_models/models/__init__.py diff --git a/enigma_models/models/box.py b/enigma_models/models/box.py new file mode 100644 index 0000000..1245606 --- /dev/null +++ b/enigma_models/models/box.py @@ -0,0 +1,65 @@ +import json + +from sqlmodel import SQLModel, Field, Session, select + +from enigma_models.database import db_engine + +# Box model +class BoxDB(SQLModel, table = True): + __tablename__ = 'boxes' + + name: str = Field(primary_key=True) + identifier: int = Field(ge=1, le=255, unique=True) + service_config: str + +# Box class +class Box: + + def __init__(self, name: str, identifier: int, service_config: dict): + self.name = name + self.identifier = identifier + self.service_config = service_config + + ####################### + # DB fetch/add + + # Tries to add the box object to the DB. If exists, it will return False, else True + def add_to_db(self) -> bool: + try: + with Session(db_engine) as session: + session.add( + BoxDB( + name=self.name, + identifier=self.identifier, + service_config=json.dumps(self.service_config) + ) + ) + session.commit() + return True + except: + return False + + # Fetches all Box from the DB + @classmethod + def find_all(cls): + boxes = [] + with Session(db_engine) as session: + db_boxes = session.exec(select(BoxDB)).all() + for box in db_boxes: + boxes.append( + Box.new( + name=box.name, + identifier=box.identifier, + data=box.service_config + ) + ) + return boxes + + # Creates a new Box object based off of DB data + @classmethod + def new(cls, name: str, identifier: int, data: str): + return cls( + name=name, + identifier=identifier, + service_config=json.loads(data) + ) \ No newline at end of file diff --git a/enigma/models/credlist.py b/enigma_models/models/credlist.py similarity index 75% rename from enigma/models/credlist.py rename to enigma_models/models/credlist.py index f198475..337d1ce 100644 --- a/enigma/models/credlist.py +++ b/enigma_models/models/credlist.py @@ -1,20 +1,31 @@ import json -from sqlmodel import Session, select +from sqlmodel import SQLModel, Field, Session, select -from enigma.engine.database import db_engine -from enigma.logger import log +from enigma_models.database import db_engine -from db_models import CredlistDB, TeamCredsDB +# Credlist model +class CredlistDB(SQLModel, table=True): + __tablename__ = 'credlists' + + name: str = Field(primary_key=True) + creds: str + +# TeamCreds model +class TeamCredsDB(SQLModel, table=True): + __tablename__ = 'teamcreds' + + name: str = Field(foreign_key='credlists.name', primary_key=True) + team_id: int = Field(foreign_key='teams.identifier', primary_key=True) + creds: str # Credlist class Credlist: - + def __init__(self, name: str, creds: dict): self.name = name self.creds = creds - log.debug(f"Created new Credlist with name {self.name}") def __repr__(self): return '<{}> named {} with creds {}'.format(type(self).__name__, self.name, self.creds) @@ -24,7 +35,6 @@ def __repr__(self): # Tries to add the Credlist object to the DB. If exists, it will return False, else True def add_to_db(self): - log.debug(f"Adding credlist {self.name} to database") try: with Session(db_engine) as session: session.add( @@ -36,13 +46,11 @@ def add_to_db(self): session.commit() return True except: - log.warning(f"Failed to add Credlist {self.name} to database!") return False # Fetches all Credlist from the DB @classmethod def find_all(cls): - log.debug(f"Retrieving all Credlists from database") credlists = [] with Session(db_engine) as session: db_credlists = session.exec(select(CredlistDB)).all() @@ -58,12 +66,12 @@ def find_all(cls): # Creates a Credlist object based off of DB data @classmethod def new(cls, name: str, creds: str): - log.debug(f"Creating new Credlist with name {name}") return cls( name=name, creds=json.loads(creds) ) - + + # TeamCreds class TeamCreds: @@ -71,27 +79,27 @@ def __init__(self, name: str, team_id: int, creds: dict): self.name = name self.team_id = team_id self.creds = creds - log.debug(f"Created new TeamCreds with name {self.name}") ####################### # DB fetch/add def add_to_db(self) -> bool: - log.debug(f"Adding team creds to database for team with ID {self.team_id}") try: with Session(db_engine) as session: session.add( - self + TeamCredsDB( + name=self.name, + team_id=self.team_id, + creds=json.dumps(self.creds) + ) ) session.commit() return True except: - log.warning(f"Failed to add team creds for team with ID {self.team_id}!") return False @classmethod def fetch_from_db(cls, name: str, team_id: int): - log.debug(f"Fetching team creds for team with ID {team_id}") with Session(db_engine) as session: db_teamcred = session.exec( select( @@ -106,7 +114,6 @@ def fetch_from_db(cls, name: str, team_id: int): @classmethod def fetch_all(cls, team_id: int): - log.debug(f"Fetching all team creds for team with ID {team_id}") with Session(db_engine) as session: db_teamcreds = session.exec( select( diff --git a/enigma_models/models/inject.py b/enigma_models/models/inject.py new file mode 100644 index 0000000..8f72f48 --- /dev/null +++ b/enigma_models/models/inject.py @@ -0,0 +1,143 @@ +import json + +from sqlmodel import SQLModel, Field, Session, select + +from enigma_models.database import db_engine + +# Inject model +class InjectDB(SQLModel, table=True): + __tablename__ = 'injects' + + id: int = Field(primary_key=True) + name: str = Field(unique=True) + desc: str + worth: int + path: str | None = None + rubric: str + +# InjectReport model +class InjectReportDB(SQLModel, table=True): + __tablename__ = 'injectreports' + + team_id: int = Field(foreign_key='teams.identifier', primary_key=True) + inject_num: int = Field(foreign_key='injects.id', primary_key=True) + score: int + breakdown: str + +# Inject class +class Inject: + + def __init__(self, id: int, name: str, desc: str, worth: int, path: str | None, rubric: dict): + self.id = id + self.name = name + self.desc = desc + self.worth = worth + self.path = path + self.rubric = rubric + + ####################### + # DB fetch/add + + # Tries to add the inject object to the DB. If exists, it will return False, else True + def add_to_db(self): + try: + with Session(db_engine) as session: + session.add( + InjectDB( + id=self.id, + name=self.name, + desc=self.desc, + worth=self.worth, + path=self.path, + rubric=json.dumps(self.rubric) + ) + ) + session.commit() + return True + except: + return False + + # Fetches all Inject from the DB + @classmethod + def find_all(cls): + injects = [] + with Session(db_engine) as session: + db_injects = session.exec(select(InjectDB)).all() + for inject in db_injects: + injects.append( + Inject.new( + id=inject.id, + name=inject.name, + desc=inject.desc, + worth=inject.worth, + path=inject.path, + rubric=json.loads(inject.rubric) + ) + ) + return injects + + # Creates an Inject object based off of DB data + @classmethod + def new(cls, id: int, name: str, desc: str, worth: int, path: str | None, rubric: str): + return cls( + id=id, + name=name, + desc=desc, + worth=worth, + path=path, + rubric=json.loads(rubric) + ) + +# Inject reports +class InjectReport: + def __init__(self, team_id: int, inject_num: int, score: int, breakdown: str): + self.team_id = team_id + self.inject_num = inject_num + self.score = score + self.breakdown = breakdown + + ####################### + # DB fetch/add + + # Tries to add the inject report object to the DB. If exists, it will return False, else True + def add_to_db(self): + try: + with Session(db_engine) as session: + session.add( + InjectReportDB( + team_id=self.team_id, + inject_num=self.inject_num, + score=self.score, + breakdown=self.breakdown + ) + ) + session.commit() + return True + except: + return False + + @classmethod + def get_report(cls, team_id: int, inject_num: int) -> tuple[int, dict]: + with Session(db_engine) as session: + db_report = session.exec( + select( + InjectReportDB + ).where( + InjectReportDB.team_id == team_id + ).where( + InjectReportDB.inject_num == inject_num + ) + ).one() + return db_report.score, json.loads(db_report.breakdown) + + @classmethod + def get_all_team_reports(cls, team_id: int)-> list[tuple[int, int]]: + with Session(db_engine) as session: + db_reports = session.exec( + select( + InjectReportDB + ).where( + InjectReportDB.team_id == team_id + ) + ).all() + return [(db_report.inject_num, db_report.score) for db_report in db_reports] \ No newline at end of file diff --git a/enigma_models/models/scorereport.py b/enigma_models/models/scorereport.py new file mode 100644 index 0000000..c072973 --- /dev/null +++ b/enigma_models/models/scorereport.py @@ -0,0 +1,39 @@ +from sqlmodel import SQLModel, Field, Session, select + +from enigma_models.database import db_engine + +# Score reports model +class ScoreReportDB(SQLModel, table=True): + __tablename__ = 'scorereports' + + team_id: int = Field(foreign_key='teams.identifier', primary_key=True) + round: int = Field(primary_key=True) + score: int + msg: str + +# Score reports +class ScoreReport: + def __init__(self, team_id: int, round: int, score: int, msg: str): + self.team_id = team_id + self.round = round + self.score = score + self.msg = msg + + ####################### + # DB fetch/add + + def add_to_db(self): + try: + with Session(db_engine) as session: + session.add( + ScoreReportDB( + team_id=self.team_id, + round=self.round, + score=self.score, + msg=self.msg + ) + ) + session.commit() + return True + except: + return False \ No newline at end of file diff --git a/enigma/models/settings.py b/enigma_models/models/settings.py similarity index 58% rename from enigma/models/settings.py rename to enigma_models/models/settings.py index d6e6a33..6ae67c0 100644 --- a/enigma/models/settings.py +++ b/enigma_models/models/settings.py @@ -1,11 +1,26 @@ -from sqlmodel import Session, select, delete +from sqlmodel import SQLModel, Field, Session, select, delete -from enigma.engine.database import db_engine -from enigma.logger import log - -from db_models import SettingsDB +from enigma_models.database import db_engine # Settings +class SettingsDB(SQLModel, table=True): + __tablename__ = 'settings' + + id: int | None = Field(default=None, primary_key=True) + competitor_info: str = Field(default='minimal') + pcr_portal: bool = Field(default=True) + inject_portal: bool = Field(default=True) + comp_name: str = Field(default='example') + check_time: int = Field(default=30) + check_jitter: int = Field(default=0, ge=0) + check_timeout: int = Field(default=5, ge=5) + check_points: int = Field(default=10, ge=1) + sla_requirement: int = Field(default=5, ge=1) + sla_penalty: int = Field(default=100, ge=0) + first_octets: str = Field(default='10.10') + first_pod_third_octet: int = Field(default=1, ge=1, le=255) + +# Settings class class Settings: def __init__(self, **kwargs): @@ -31,7 +46,6 @@ def __init__(self, **kwargs): ####################### # DB fetch/add def add_to_db(self): - log.debug(f'Adding settings to database') with Session(db_engine) as session: session.exec(delete(SettingsDB)) session.commit() @@ -48,7 +62,6 @@ def add_to_db(self): @classmethod def get_setting(cls, key: str): - log.debug(f'Locating setting: {key}') with Session(db_engine) as session: settings = session.exec(select(SettingsDB)).one() return getattr(settings, key) \ No newline at end of file diff --git a/enigma/models/slareport.py b/enigma_models/models/slareport.py similarity index 61% rename from enigma/models/slareport.py rename to enigma_models/models/slareport.py index 73dbcd5..6d0dbbd 100644 --- a/enigma/models/slareport.py +++ b/enigma_models/models/slareport.py @@ -1,9 +1,14 @@ -from sqlmodel import Session +from sqlmodel import SQLModel, Field, Session -from enigma.logger import log -from enigma.engine.database import db_engine +from enigma_models.database import db_engine -from db_models import SLAReportDB +# SLA Report +class SLAReportDB(SQLModel, table=True): + __tablename__ = 'slareports' + + team_id: int = Field(foreign_key='teams.identifier', primary_key=True) + round: int = Field(primary_key=True) + service: str = Field(primary_key=True) # SLA Report class SLAReport: @@ -17,7 +22,6 @@ def __init__(self, team_id: int, round: int, service: str): # DB fetch/add def add_to_db(self): - log.debug(f'Adding SLAReport for team {self.team_id} during round {self.round}') with Session(db_engine) as session: session.add( SLAReportDB( diff --git a/enigma_models/models/team.py b/enigma_models/models/team.py new file mode 100644 index 0000000..b7822c3 --- /dev/null +++ b/enigma_models/models/team.py @@ -0,0 +1,79 @@ +from sqlmodel import SQLModel, Field, Session, select + +from enigma_models.database import db_engine + +# Team model +class RvBTeamDB(SQLModel, table=True): + __tablename__ = 'teams' + + name: str = Field(primary_key=True, foreign_key='parableusers.name') + identifier: int = Field(ge=1, le=255, unique=True) + score: int + +# Team class +class RvBTeam: + + def __init__(self, name: str, identifier: int, score: int): + self.name = name + self.identifier = identifier + self.score = score + +####################### + # DB fetch/add + + # Tries to add the competitor object to the DB. If exists, it will return False, else True + def add_to_db(self): + try: + with Session(db_engine) as session: + session.add( + RvBTeamDB( + name=self.name, + identifier=self.identifier, + score=self.score + ) + ) + session.commit() + return True + except: + return False + + # Updates score in DB + def update_in_db(self): + with Session(db_engine) as session: + session.exec( + select( + RvBTeamDB + ).where( + RvBTeamDB.identifier == self.identifier + ) + ).one().score = self.score + session.commit() + + # Fetches all Team from the DB + @classmethod + def find_all(cls): + teams = [] + with Session(db_engine) as session: + db_teams = session.exec( + select( + RvBTeamDB + ) + ).all() + for db_team in db_teams: + teams.append( + RvBTeam( + name=db_team.name, + identifier=db_team.identifier, + score=db_team.score + ) + ) + return teams + + # Creates a new Team from the config info + @classmethod + def new(cls, name: str, identifier: int, score: int): + return cls( + name=name, + identifier=identifier, + score=score + ) \ No newline at end of file diff --git a/praxos/models/user.py b/enigma_models/models/user.py similarity index 56% rename from praxos/models/user.py rename to enigma_models/models/user.py index 7c20c07..9f9bc3c 100644 --- a/praxos/models/user.py +++ b/enigma_models/models/user.py @@ -1,15 +1,28 @@ import secrets, string +from enum import Enum -from sqlmodel import Session, select +from sqlmodel import SQLModel, Field, Session, select -from db_models import ParableUserDB -from db_models.auth import get_hash +from enigma_models.database import db_engine +from enigma_models.auth import get_hash, verify_hash -from praxos.logger import log -from praxos.database import db_engine +# ParableUser model +class ParableUserDB(SQLModel, table=True): + __tablename__ = 'parableusers' + name: str = Field(primary_key=True) + identifier: int = Field(ge=1, le=255, unique=True) + permission_level: int = Field(ge=0, le=2) + pw_hash: bytes | None = Field(default=None) + +# ParableUser class class ParableUser: + class Permission(Enum): + ADMINISTRATOR = 0 + GREEN = 1 + USER = 2 + def __init__(self, username: str, identifier: int, permission_level: int, pw_hash: bytes=None): self.username = username self.identifier = identifier @@ -22,8 +35,13 @@ def create_pw(self, length: int): self.pw_hash = get_hash(password) return password + def set_pw(self, password: str): + self.pw_hash = get_hash(password) + + def check_pw(self, password: str): + return verify_hash(password, self.pw_hash) + def add_to_db(self): - log.debug(f"Adding Parable user {self.username} to DB") try: with Session(db_engine) as session: session.add( @@ -37,11 +55,9 @@ def add_to_db(self): session.commit() return True except: - log.warning(f"Failed to add Parable user {self.username} to DB") return False def remove_from_db(self) -> bool: - log.debug(f'Removing Parable user {self.username} from database') try: with Session(db_engine) as session: user = session.exec( @@ -54,14 +70,12 @@ def remove_from_db(self) -> bool: session.delete(user) session.commit() except: - log.warning(f'Failed to remove Parable user {self.name} from database!') return False return True @classmethod def last_identifier(cls): with Session(db_engine) as session: - log.debug(f'Retrieving last identifier from database') last_user = session.exec( select( ParableUserDB @@ -73,10 +87,41 @@ def last_identifier(cls): return 0 return last_user.identifier - # Fetches all Parable user from the DB + @classmethod + def find(cls, username: str=None, identifier: int=None): + with Session(db_engine) as session: + try: + if username is not None: + user = session.exec( + select( + ParableUserDB + ).where( + ParableUserDB.name == username + ) + ).one() + elif identifier is not None: + user = session.exec( + select( + ParableUserDB + ).where( + ParableUserDB.identifier == identifier + ) + ).one() + except: + return None + + if user is not None: + return ParableUser( + username=user.name, + identifier=user.identifier, + permission_level=user.permission_level, + pw_hash=user.pw_hash + ) + return None + + # Fetches all Parable competitor from the DB @classmethod def find_all(cls) -> list: - log.debug(f'Retrieving all Parable users from database') users = [] with Session(db_engine) as session: db_users = session.exec( diff --git a/example_configs/teams/teams.csv b/example_configs/teams/teams.csv index a4b717c..c462677 100644 --- a/example_configs/teams/teams.csv +++ b/example_configs/teams/teams.csv @@ -1,2 +1,2 @@ coolteam,entangled,tod.fu,saveme2 -radteam,sp1n0. \ No newline at end of file +radteam,sp1n0.,mayoflayo, \ No newline at end of file diff --git a/main/enigma/main.py b/main/enigma/main.py index 0d69c0a..a789749 100644 --- a/main/enigma/main.py +++ b/main/enigma/main.py @@ -3,9 +3,9 @@ from enigma.logger import log, write_log_header from enigma.engine.cmd import RvBCMD -from enigma.engine.database import del_db, init_db +from enigma_models.database import del_db, init_db -from enigma.models.settings import Settings +from enigma_models.models.settings import Settings # main.py [OPTIONS] # -r, --reset Does a reset of the database diff --git a/main/parable/app.py b/main/parable/app.py deleted file mode 100644 index e1fe91c..0000000 --- a/main/parable/app.py +++ /dev/null @@ -1,7 +0,0 @@ -from flask import Flask - -app = Flask('parable') - -@app.route('/') -def index(): - return "

Hello World

" \ No newline at end of file diff --git a/main/parable/main.py b/main/parable/main.py new file mode 100644 index 0000000..c16d053 --- /dev/null +++ b/main/parable/main.py @@ -0,0 +1,60 @@ +from contextlib import asynccontextmanager +import uvicorn + +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.authentication import AuthenticationMiddleware +from starlette.middleware.sessions import SessionMiddleware +from starlette.routing import Router as StarletteRouter, Mount, Route +from starlette.responses import FileResponse + +import parable +from parable.logger import log_config, write_log_header +from parable.auth import ParableAuthBackend, auth_routes +from parable import secret_key +from parable import templates, static +from parable.dashboard import dashboard + +# Routes +async def index(request): + template = "index.html" + context = {"request": request} + return templates.TemplateResponse(template, context) + +async def favicon(request): + return FileResponse('static/favicon.ico') + +routes = [ + Route('/', endpoint=index), + #Route('/favicon.ico', endpoint=favicon), + Route('/dashboard', endpoint=dashboard, methods=['GET']), + Mount('/auth', routes=auth_routes), + Mount('/static', static, name='static') +] + +# Middleware +middleware = [ + Middleware(AuthenticationMiddleware, backend=ParableAuthBackend()) +] + +# Lifespan handler +@asynccontextmanager +async def lifespan(app): + write_log_header() + yield + +# Application creation +app = Starlette( + debug=True, + lifespan=lifespan, + routes=routes, + middleware=middleware +) + +if __name__ == '__main__': + uvicorn.run( + 'main:app', + host='0.0.0.0', + port=5070, + log_config=log_config + ) \ No newline at end of file diff --git a/main/praxos/main.py b/main/praxos/main.py index e98db8d..5ba4cca 100644 --- a/main/praxos/main.py +++ b/main/praxos/main.py @@ -11,14 +11,13 @@ from discord.ext import commands from praxos.logger import log, write_log_header -from praxos.models.settings import Settings -from praxos.models.team import RvBTeam -from praxos.models.box import Box -from praxos.models.user import ParableUser +from enigma_models.models.settings import Settings +from enigma_models.models.team import RvBTeam +from enigma_models.models.box import Box +from enigma_models.models.user import ParableUser ############### # TODO: Add "praxos event" creation -# TODO: Add team creds load_dotenv(override=True) @@ -122,7 +121,7 @@ async def init(ctx: commands.context.Context): comp_cat = await guild.create_category(name=comp_name, overwrites=main_overwrites) await comp_cat.create_text_channel(name='announcements', overwrites=announcement_overwrites) await comp_cat.create_text_channel(name='general', overwrites=main_overwrites) - await comp_cat.create_text_channel(name='green-team-alert', overwrites=gt_overwrites) + await comp_cat.create_text_channel(name='green-competitor-alert', overwrites=gt_overwrites) await comp_cat.create_text_channel(name='dev-general', overwrites=dev_overwrites) await comp_cat.create_voice_channel(name='dev-voice', overwrites=dev_overwrites) await comp_cat.create_voice_channel(name='general', overwrites=main_overwrites) @@ -202,8 +201,8 @@ async def create_teams(ctx: commands.context.Context): } team_cat = await guild.create_category(name=f'{comp_name} {teamname}', overwrites=team_overwrites) - await team_cat.create_text_channel(name='team-chat', overwrites=team_overwrites) - await team_cat.create_voice_channel(name='team-voice', overwrites=team_overwrites) + await team_cat.create_text_channel(name='competitor-chat', overwrites=team_overwrites) + await team_cat.create_voice_channel(name='competitor-voice', overwrites=team_overwrites) for teammate in row: member = discord.utils.get(guild.members, name=teammate) @@ -219,12 +218,12 @@ async def create_teams(ctx: commands.context.Context): identifier = identifier + 1 csvfile = StringIO() - fieldnames = ['team', 'password'] + fieldnames = ['competitor', 'password'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) for team, password in username_pw_combos.items(): - writer.writerow({'team': team, 'password': password}) + writer.writerow({'competitor': team, 'password': password}) csvfile.seek(0) buffer = BytesIO() @@ -248,12 +247,12 @@ async def delete_teams(ctx: commands.context.Context): competitor_role_re = re.compile(r'^Team\s[a-zA-Z0-9]+$') competitor_cat_re = re.compile(fr'^{comp_name}\s[a-zA-Z0-9]+$') - log.debug("Removing team roles") + log.debug("Removing competitor roles") for role in guild.roles: if competitor_role_re.match(role.name) is not None: await role.delete() - log.debug("Removing team categories and channels") + log.debug("Removing competitor categories and channels") for category in guild.categories: if competitor_cat_re.match(category.name): for channel in category.channels: @@ -274,7 +273,7 @@ async def delete_teams(ctx: commands.context.Context): await ctx.send('Finished! Deleted teams') -# Green team support commands +# Green competitor support commands @bot.command(pass_context=True) @commands.check_any(commands.has_role(f'{Settings.get_setting('comp_name')} Competitor'), commands.has_role("Green Team"), @@ -283,7 +282,7 @@ async def delete_teams(ctx: commands.context.Context): async def request(ctx: commands.context.Context, *args): log.info('Command \'request\' invoked. Someone has a GT request.') guild = discord.utils.get(bot.guilds, id=guild_id) - gt_alert_channel = discord.utils.get(guild.text_channels, name='green-team-alert') + gt_alert_channel = discord.utils.get(guild.text_channels, name='green-competitor-alert') gt_role = discord.utils.get(guild.roles, name='Green Team') competitor_role_re = re.compile(r'^Team\s[a-zA-Z0-9]+$') for role in ctx.author.roles: diff --git a/parable/__init__.py b/parable/__init__.py index 215d15c..9aa0fa1 100644 --- a/parable/__init__.py +++ b/parable/__init__.py @@ -1,18 +1,17 @@ -import os +from os import getcwd, getenv +from os.path import join -from flask import Flask - -from os import getenv from dotenv import load_dotenv +from starlette.templating import Jinja2Templates +from starlette.staticfiles import StaticFiles + load_dotenv(override=True) -postgres_settings = { - 'user': getenv('POSTGRES_USER'), - 'password': getenv('POSTGRES_PASSWORD'), - 'host': getenv('POSTGRES_HOST'), - 'port': getenv('POSTGRES_PORT') -} +templates = Jinja2Templates(directory=join(getcwd(), 'parable', 'templates')) + +static = StaticFiles(directory=join(getcwd(), 'parable', 'static')) + +secret_key = getenv('PARABLE_SECRET_KEY') -def create_app(test_config=None): - pass \ No newline at end of file +token_age = 60 * 60 \ No newline at end of file diff --git a/parable/auth.py b/parable/auth.py index e69de29..af335a7 100644 --- a/parable/auth.py +++ b/parable/auth.py @@ -0,0 +1,137 @@ +import logging +from datetime import datetime, timedelta, timezone + +import base64 +import jwt +from jwt.exceptions import InvalidTokenError + +from starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError, BaseUser +from starlette.responses import JSONResponse, RedirectResponse, Response +from starlette.routing import Route + +from enigma_models.models.user import ParableUser as DBUser +from enigma_models.auth import verify_hash + +from parable import templates, secret_key, token_age + +log = logging.getLogger('uvicorn') + +# Create Bearer tokens +def create_access_token(data: dict, expires_delta: timedelta | None = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + timedelta(minutes=10) + to_encode.update({ + 'exp': expire + }) + return jwt.encode(to_encode, secret_key, algorithm='HS256') + +def check_token(payload: dict): + expiry = datetime.fromtimestamp(int(payload.get("exp")), timezone.utc) + # If auth token is expired, direct user to login page + if expiry < datetime.now(timezone.utc): + return RedirectResponse(url='/auth/login') + + # Issue new token if token is almost expired + if expiry < datetime.now(timezone.utc) + timedelta(minutes=15): + pass + +# Simple user class to pass around after authentication +class ParableUser(BaseUser): + + def __init__(self, username: str, identifier: int): + self.username = username + self.identifier = identifier + + @property + def is_authenticated(self) -> bool: + return True + + @property + def display_name(self) -> str: + return self.username + +# Authentication backend +class ParableAuthBackend(AuthenticationBackend): + async def authenticate(self, conn): + token = conn.cookies.get('token') + if token is None: + return + + # + try: + payload = jwt.decode(token, secret_key, algorithms=['HS256']) + username: str = payload.get("sub") + if username is None: + raise AuthenticationError('Invalid token, no user provided') + except InvalidTokenError: + raise AuthenticationError('Invalid token') + + user = DBUser.find(username=username) + if user is None: + raise AuthenticationError('Invalid token, invalid user') + + auth_user = ParableUser(user.username, user.identifier) + + scope = [] + match user.permission_level: + case user.Permission.ADMINISTRATOR: + scope.append('admin') + case user.Permission.GREEN: + scope.append('green') + case _: + scope.append('user') + + auth_credentials = AuthCredentials(scope) + + return auth_credentials, auth_user + +# Authentication routes + +async def login(request): + template = 'auth/login.html' + context = {'request': request} + return templates.TemplateResponse(request, template) + +async def logout(request): + template = 'logout.html' + context = {'request': request} + return templates.TemplateResponse(request, template) + +async def get_token(request): + b64credentials = request.headers['Authorization'] + credentials = base64.b64decode(b64credentials.split(' ')[1]).split(b':') + + username = credentials[0].decode() + pw = credentials[1] + log.info(f'Login request from {request.client.host} for {username}') + + user = DBUser.find(username=username) + if user is None: + log.info(f'Login request from {request.client.host} for {username} failed: invalid username') + return JSONResponse({'error': 'Invalid credentials'}, status_code=401) + + result = verify_hash(plain_pw=pw, hashed_pw=user.pw_hash) + if not result: + log.info(f'Login request from {request.client.host} for {username} failed: invalid password') + return JSONResponse({'error': 'Invalid credentials'}, status_code=401) + + log.info(f'Login request from {request.client.host} for {username} successful, issuing token') + + success_response = JSONResponse({'ok': 'true'}) + success_response.set_cookie( + key='token', + value=create_access_token(data={'sub': username}, expires_delta=timedelta(seconds=token_age)), + httponly=True, + ) + + return success_response + +# Routes +auth_routes = [ + Route('/login', endpoint=login, methods=['GET']), + Route('/logout', endpoint=logout, methods=['GET']), + Route('/token', endpoint=get_token, methods=['POST']) +] \ No newline at end of file diff --git a/parable/competitor.py b/parable/competitor.py new file mode 100644 index 0000000..3540d61 --- /dev/null +++ b/parable/competitor.py @@ -0,0 +1,3 @@ + + +competitor_routes = [] \ No newline at end of file diff --git a/parable/dashboard.py b/parable/dashboard.py new file mode 100644 index 0000000..625d2e0 --- /dev/null +++ b/parable/dashboard.py @@ -0,0 +1,8 @@ +from starlette.responses import JSONResponse + +from parable import templates + +async def dashboard(request): + template = 'dashboard.html' + context = {'request': request} + return templates.TemplateResponse(template, context) \ No newline at end of file diff --git a/parable/database.py b/parable/database.py deleted file mode 100644 index e4e974b..0000000 --- a/parable/database.py +++ /dev/null @@ -1,8 +0,0 @@ -from sqlmodel import create_engine - -from parable import postgres_settings - -db_engine = create_engine( - f'postgresql+psycopg://{postgres_settings['user']}:{postgres_settings['password']}@{postgres_settings['host']}:{postgres_settings['port']}/enigma', - echo=False -) \ No newline at end of file diff --git a/parable/exceptions.py b/parable/exceptions.py new file mode 100644 index 0000000..e06a8db --- /dev/null +++ b/parable/exceptions.py @@ -0,0 +1,4 @@ +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.responses import HTMLResponse + diff --git a/parable/logger.py b/parable/logger.py index e69de29..60060c6 100644 --- a/parable/logger.py +++ b/parable/logger.py @@ -0,0 +1,48 @@ +from os import getenv, getcwd +from os.path import join + +from dotenv import load_dotenv + +from uvicorn.config import LOGGING_CONFIG + +load_dotenv(override=True) + +#### Creates a universal logger for Parable + +log_config = LOGGING_CONFIG + +log_level = getenv('LOG_LEVEL') +logs_path = join(getcwd(), 'logs') + +log_file = join(logs_path, 'parable.log') + +# Writing a header to the log file because it looks better +def write_log_header(): + with open(log_file, 'w+') as f: + f.writelines([ + '++++==== Parable Web Interface Log ====++++\n' + ]) + +log_config['formatters'].update({ + 'file': { + '()': 'uvicorn.logging.DefaultFormatter', + 'fmt': '{asctime} {levelprefix} {message}', + 'datefmt': '%Y-%m-%d %H:%M:%S', + 'style': '{', + 'use_colors': False + } +}) +log_config['handlers'].update({ + 'file': { + 'formatter': 'file', + 'class': 'logging.FileHandler', + 'mode': 'a', + 'filename': log_file + } +}) +log_config['loggers']['uvicorn']['handlers'].append( + 'file' +) +log_config['loggers']['uvicorn.access']['handlers'].append( + 'file' +) \ No newline at end of file diff --git a/parable/models/inject.py b/parable/models/inject.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/scoreport.py b/parable/models/scoreport.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/settings.py b/parable/models/settings.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/slareport.py b/parable/models/slareport.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/team.py b/parable/models/team.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/user.py b/parable/models/user.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/rabbitmq.py b/parable/rabbitmq.py new file mode 100644 index 0000000..2795795 --- /dev/null +++ b/parable/rabbitmq.py @@ -0,0 +1,51 @@ +import pika + +from enigma.engine import rabbitmq_settings + +class RabbitMQ: + + def __init__(self): + self.user = rabbitmq_settings['competitor'] + self.password = rabbitmq_settings['password'] + self.host = rabbitmq_settings['host'] + self.port = rabbitmq_settings['port'] + self.connection = None + self.channel = None + self.connect() + + def connect(self): + credentials = pika.PlainCredentials(self.user, self.password) + parameters = pika.ConnectionParameters( + host=self.host, + port=self.port, + credentials=credentials + ) + self.connection = pika.BlockingConnection(parameters) + self.channel = self.connection.channel() + + def close(self): + if self.connection and not self.connection.is_closed: + self.connection.close() + + def consume(self, queue_name: str, callback): + if not self.channel: + raise Exception('Connection is not established') + self.channel.basic_consume( + queue=queue_name, + on_message_callback=callback, + auto_ack=True + ) + self.channel.start_consuming() + + def publish(self, queue_name, message: str): + if not self.channel: + raise Exception('Connection is not established') + self.channel.queue_declare(queue=queue_name, durable=True) + self.channel.basic_publish( + exchange='', + routing_key=queue_name, + body=message, + properties=pika.BasicProperties( + delivery_mode=2 + ) + ) diff --git a/parable/requirements.txt b/parable/requirements.txt new file mode 100644 index 0000000..0352cb5 Binary files /dev/null and b/parable/requirements.txt differ diff --git a/parable/routes.py b/parable/routes.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/box.py b/parable/static/css/parable.css similarity index 100% rename from parable/models/box.py rename to parable/static/css/parable.css diff --git a/parable/static/favicon.ico b/parable/static/favicon.ico new file mode 100644 index 0000000..49ebbd0 Binary files /dev/null and b/parable/static/favicon.ico differ diff --git a/parable/static/scores/team1_final.csv b/parable/static/scores/team1_final.csv new file mode 100644 index 0000000..01599c5 --- /dev/null +++ b/parable/static/scores/team1_final.csv @@ -0,0 +1,7 @@ +point_category,raw_points,penalty_points,total_points +total,0,500,-500 +examplebox.http,0,100,-100 +examplebox.https,0,100,-100 +examplebox.ssh,0,100,-100 +examplebox2.http,0,100,-100 +examplebox2.ssh,0,100,-100 diff --git a/parable/static/scores/team2_final.csv b/parable/static/scores/team2_final.csv new file mode 100644 index 0000000..413f501 --- /dev/null +++ b/parable/static/scores/team2_final.csv @@ -0,0 +1,7 @@ +point_category,raw_points,penalty_points,total_points +total,40,200,-160 +examplebox.http,10,0,10 +examplebox.https,10,0,10 +examplebox.ssh,0,100,-100 +examplebox2.http,20,0,20 +examplebox2.ssh,0,100,-100 diff --git a/parable/static/scores/team3_final.csv b/parable/static/scores/team3_final.csv new file mode 100644 index 0000000..2b5fff2 --- /dev/null +++ b/parable/static/scores/team3_final.csv @@ -0,0 +1,7 @@ +point_category,raw_points,penalty_points,total_points +total,20,300,-280 +examplebox.http,10,0,10 +examplebox.https,10,0,10 +examplebox.ssh,0,100,-100 +examplebox2.http,0,100,-100 +examplebox2.ssh,0,100,-100 diff --git a/parable/static/scores/team4_final.csv b/parable/static/scores/team4_final.csv new file mode 100644 index 0000000..e5f130b --- /dev/null +++ b/parable/static/scores/team4_final.csv @@ -0,0 +1,7 @@ +point_category,raw_points,penalty_points,total_points +total,30,300,-270 +examplebox.http,0,100,-100 +examplebox.https,10,0,10 +examplebox.ssh,0,100,-100 +examplebox2.http,20,0,20 +examplebox2.ssh,0,100,-100 diff --git a/parable/static/scores/team5_final.csv b/parable/static/scores/team5_final.csv new file mode 100644 index 0000000..b825e50 --- /dev/null +++ b/parable/static/scores/team5_final.csv @@ -0,0 +1,7 @@ +point_category,raw_points,penalty_points,total_points +total,50,200,-150 +examplebox.http,20,0,20 +examplebox.https,20,0,20 +examplebox.ssh,0,100,-100 +examplebox2.http,10,0,10 +examplebox2.ssh,0,100,-100 diff --git a/parable/models/credlist.py b/parable/static/scss/parable.scss similarity index 100% rename from parable/models/credlist.py rename to parable/static/scss/parable.scss diff --git a/parable/static/style.css b/parable/static/style.css deleted file mode 100644 index e69de29..0000000 diff --git a/parable/templates/admin/box.html b/parable/templates/admin/box.html new file mode 100644 index 0000000..083b709 --- /dev/null +++ b/parable/templates/admin/box.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Box Management{% endblock %}

+{% endblock %} \ No newline at end of file diff --git a/parable/templates/admin/create_team.html b/parable/templates/admin/credlist.html similarity index 100% rename from parable/templates/admin/create_team.html rename to parable/templates/admin/credlist.html diff --git a/parable/templates/admin/engine.html b/parable/templates/admin/engine.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/admin/engine.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/admin/inject.html b/parable/templates/admin/inject.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/admin/inject.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/admin/settings.html b/parable/templates/admin/settings.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/admin/settings.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/admin/team.html b/parable/templates/admin/team.html new file mode 100644 index 0000000..e108ad2 --- /dev/null +++ b/parable/templates/admin/team.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Team Management{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + + + + + + +
+{% endblock %} \ No newline at end of file diff --git a/parable/templates/auth/login.html b/parable/templates/auth/login.html index 566549b..3933693 100644 --- a/parable/templates/auth/login.html +++ b/parable/templates/auth/login.html @@ -1,10 +1,47 @@ - - - - - Title - - - - - \ No newline at end of file +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Log In{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+ +
+ +
+ + + +{% endblock %} \ No newline at end of file diff --git a/parable/templates/auth/unauthorized.html b/parable/templates/auth/unauthorized.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/auth/unauthorized.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/base.html b/parable/templates/base.html index 566549b..85dc9ba 100644 --- a/parable/templates/base.html +++ b/parable/templates/base.html @@ -1,10 +1,21 @@ - - - Title + + - +{% block title %}{% endblock %} + + +
+
+ {% block header %}{% endblock %} +
+ {% block content %}{% endblock %} +
\ No newline at end of file diff --git a/parable/templates/competitor/pcr.html b/parable/templates/competitor/pcr.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/competitor/pcr.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/competitor/service.html b/parable/templates/competitor/service.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/competitor/service.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/competitor/summary.html b/parable/templates/competitor/summary.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/competitor/summary.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/dashboard.html b/parable/templates/dashboard.html new file mode 100644 index 0000000..8a82df2 --- /dev/null +++ b/parable/templates/dashboard.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Dashboard{% endblock %}

+{% endblock %} \ No newline at end of file diff --git a/parable/templates/index.html b/parable/templates/index.html new file mode 100644 index 0000000..c3f975d --- /dev/null +++ b/parable/templates/index.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Welcome to Parable!{% endblock %}

+{% endblock %} \ No newline at end of file diff --git a/parable/views.py b/parable/views.py deleted file mode 100644 index e69de29..0000000 diff --git a/praxos/__init__.py b/praxos/__init__.py index 4d1de2b..fc8b0ba 100644 --- a/praxos/__init__.py +++ b/praxos/__init__.py @@ -4,7 +4,7 @@ load_dotenv(override=True) postgres_settings = { - 'user': getenv('POSTGRES_USER'), + 'competitor': getenv('POSTGRES_USER'), 'password': getenv('POSTGRES_PASSWORD'), 'host': getenv('POSTGRES_HOST'), 'port': getenv('POSTGRES_PORT') diff --git a/praxos/database.py b/praxos/database.py deleted file mode 100644 index b6b0006..0000000 --- a/praxos/database.py +++ /dev/null @@ -1,8 +0,0 @@ -from sqlmodel import create_engine - -from praxos import postgres_settings - -db_engine = create_engine( - f'postgresql+psycopg://{postgres_settings['user']}:{postgres_settings['password']}@{postgres_settings['host']}:{postgres_settings['port']}/enigma', - echo=False -) \ No newline at end of file diff --git a/praxos/logger.py b/praxos/logger.py index 6d3f434..a2bc785 100644 --- a/praxos/logger.py +++ b/praxos/logger.py @@ -1,4 +1,5 @@ import logging +from logging import FileHandler from os import getenv, getcwd from os.path import join @@ -32,7 +33,7 @@ def write_log_header(): ) # Handlers for file and stream output -file_handler = logging.FileHandler( +file_handler = FileHandler( log_file, mode = 'a', encoding = 'utf-8' diff --git a/praxos/models/__init__.py b/praxos/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/praxos/models/box.py b/praxos/models/box.py deleted file mode 100644 index 03f9c30..0000000 --- a/praxos/models/box.py +++ /dev/null @@ -1,25 +0,0 @@ -from sqlmodel import Session, select - -from db_models import BoxDB - -from praxos.logger import log -from praxos.database import db_engine - -class Box: - - def __init__(self, name: str): - self.name = name - - @classmethod - def find_all(cls): - log.debug(f"Retrieving all boxes from database") - boxes = [] - with Session(db_engine) as session: - db_boxes = session.exec(select(BoxDB)).all() - for box in db_boxes: - boxes.append( - Box( - name=box.name - ) - ) - return boxes \ No newline at end of file diff --git a/praxos/models/settings.py b/praxos/models/settings.py deleted file mode 100644 index 8ff798a..0000000 --- a/praxos/models/settings.py +++ /dev/null @@ -1,35 +0,0 @@ -from sqlmodel import Session, select - -from praxos.database import db_engine -from praxos.logger import log - -from db_models import SettingsDB - -class Settings: - - def __init__(self, **kwargs): - setting_keys = [ - 'id', - 'competitor_info', - 'pcr_portal', - 'inject_portal', - 'comp_name', - 'check_time', - 'check_jitter', - 'check_timeout', - 'check_points', - 'sla_requirement', - 'sla_penalty', - 'first_octets', - 'first_pod_third_octet' - ] - for k, v in kwargs.items(): - if k in setting_keys: - setattr(self, k, v) - - @classmethod - def get_setting(cls, key: str): - log.debug(f'Locating setting: {key}') - with Session(db_engine) as session: - settings = session.exec(select(SettingsDB)).one() - return getattr(settings, key) \ No newline at end of file diff --git a/praxos/models/team.py b/praxos/models/team.py deleted file mode 100644 index 829b91b..0000000 --- a/praxos/models/team.py +++ /dev/null @@ -1,71 +0,0 @@ -from sqlmodel import Session, select - -from db_models import RvBTeamDB - -from praxos.logger import log -from praxos.database import db_engine - -class RvBTeam: - - def __init__(self, name: str, identifier: int, score: int): - self.name = name - self.identifier = identifier - self.score = score - - # Tries to add the team object to the DB. If exists, it will return False, else True - def add_to_db(self) -> bool: - log.debug(f'Adding Team {self.name} to database') - try: - with Session(db_engine) as session: - session.add( - RvBTeamDB( - name=self.name, - identifier=self.identifier, - score=self.score - ) - ) - session.commit() - return True - except: - log.warning(f'Failed to add Team {self.name} to database!') - return False - - # Tries to remove the team object from the DB. If it doesn't exist, it will return False, else True - def remove_from_db(self) -> bool: - log.debug(f'Removing Team {self.name} from database') - try: - with Session(db_engine) as session: - team = session.exec( - select( - RvBTeamDB - ).where( - RvBTeamDB.name == self.name - ) - ).one() - session.delete(team) - session.commit() - except: - log.warning(f'Failed to remove Team {self.name} from database!') - return False - return True - - # Fetches all Team from the DB - @classmethod - def find_all(cls) -> list: - log.debug(f'Retrieving all teams from database') - teams = [] - with Session(db_engine) as session: - db_teams = session.exec( - select( - RvBTeamDB - ) - ).all() - for db_team in db_teams: - teams.append( - RvBTeam( - name=db_team.name, - identifier=db_team.identifier, - score=db_team.score - ) - ) - return teams \ No newline at end of file diff --git a/enigma/requirements.txt b/requirements/enigma/requirements.txt similarity index 100% rename from enigma/requirements.txt rename to requirements/enigma/requirements.txt diff --git a/requirements/parable/requirements.txt b/requirements/parable/requirements.txt new file mode 100644 index 0000000..7a7f97c Binary files /dev/null and b/requirements/parable/requirements.txt differ diff --git a/setup.sh b/setup.sh index e69de29..42418f5 100644 --- a/setup.sh +++ b/setup.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Updates +apt update +apt dist-upgrade -y +apt autoremove + +# Install dependencies +apt install build-essential gdb lcov pkg-config \ + libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \ + libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \ + lzma lzma-dev tk-dev uuid-dev zlib1g-dev curl git + +# Install Python 3.13.2 +curl -L -O https://www.python.org/ftp/python/3.13.2/Python-3.13.2.tgz +tar -xf Python-3.13.2.tgz + +cd Python-3.13.2 + +./configure --enable-optimizations + +make +make altinstall + +cd .. + +git clone https://github.com/EntangledLabs/Enigma.git diff --git a/static/.gitkeep b/static/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test.py b/test.py index 3d46092..be7105e 100644 --- a/test.py +++ b/test.py @@ -2,14 +2,13 @@ from os.path import join, isfile, splitext from os import listdir -from sqlmodel import create_engine - -from enigma.engine.database import del_db, init_db +from enigma_models.database import del_db, init_db from enigma.models.box import Box -from enigma.models.settings import Settings -from enigma.models.credlist import Credlist from enigma.models.team import RvBTeam +from enigma_models.models.user import ParableUser +from enigma_models.models.settings import Settings +from enigma_models.models.credlist import Credlist from enigma.broker import RabbitMQ boxes_path = './example_configs/boxes' @@ -53,17 +52,38 @@ credlist.add_to_db() #print('teams') -"""teams = [] +teams = [] +users = [] for i in range(5): + user = ParableUser( + username=f'coolteam{i+1}', + identifier=i+1, + permission_level=2 + ) + pw = user.create_pw(12) + users.append((user, pw)) + user.add_to_db() + team = RvBTeam( name=f'coolteam{i+1}', identifier=i+1, services=Box.all_service_names(boxes) ) teams.append(team) - team.add_to_db()""" + team.add_to_db() + +admin_user = ParableUser( + username='admin', + identifier=0, + permission_level=0 +) +admin_pw = admin_user.create_pw(12) +users.append((admin_user, admin_pw)) +admin_user.add_to_db() + +Settings(first_octets='10.10', sla_requirement=2).add_to_db() -Settings(first_octets='10.10', sla_requirement=2) +print([(user[0].username, user[1]) for user in users]) while True: cmd = input('Enter command: ')