From 8fd13911db392aa91145ab5ade71f337dbf7eff2 Mon Sep 17 00:00:00 2001 From: EntangledLabs Date: Mon, 23 Dec 2024 16:15:53 -0800 Subject: [PATCH 01/10] unified DB access/models, created base views for parable --- README.md | 4 +- db_models/__init__.py | 96 ------------ docker/enigma/Dockerfile | 2 +- docker/praxos/Dockerfile | 2 +- enigma/engine/__init__.py | 7 + enigma/engine/cmd.py | 8 +- enigma/engine/database.py | 38 ----- enigma/engine/scoring.py | 6 +- enigma/logger.py | 3 +- enigma/models/box.py | 70 ++------- enigma/models/inject.py | 114 +------------- enigma/models/scorereport.py | 30 ---- enigma/models/team.py | 81 ++-------- enigma_models/__init__.py | 11 ++ {db_models => enigma_models}/auth.py | 0 enigma_models/database.py | 32 ++++ {parable => enigma_models}/models/__init__.py | 0 enigma_models/models/box.py | 65 ++++++++ {enigma => enigma_models}/models/credlist.py | 41 ++--- enigma_models/models/inject.py | 143 ++++++++++++++++++ enigma_models/models/scorereport.py | 39 +++++ {enigma => enigma_models}/models/settings.py | 27 +++- {enigma => enigma_models}/models/slareport.py | 14 +- {praxos => enigma_models}/models/team.py | 64 ++++---- {praxos => enigma_models}/models/user.py | 55 +++++-- main/enigma/main.py | 4 +- main/parable/app.py | 14 +- main/praxos/main.py | 8 +- parable/__init__.py | 31 ++-- parable/admin.py | 24 +++ parable/auth.py | 86 +++++++++++ parable/database.py | 8 - parable/logger.py | 52 +++++++ parable/models/box.py | 0 parable/models/credlist.py | 0 parable/models/inject.py | 0 parable/models/scoreport.py | 0 parable/models/settings.py | 0 parable/models/slareport.py | 0 parable/models/team.py | 0 parable/models/user.py | 0 parable/routes.py | 0 .../admin/{create_team.html => box.html} | 0 parable/templates/admin/credlist.html | 10 ++ parable/templates/admin/engine.html | 10 ++ parable/templates/admin/inject.html | 10 ++ parable/templates/admin/settings.html | 10 ++ parable/templates/admin/team.html | 10 ++ parable/templates/admin/user.html | 10 ++ parable/templates/auth/login.html | 23 +-- parable/templates/auth/unauthorized.html | 10 ++ parable/templates/base.html | 33 ++-- parable/templates/index.html | 5 + parable/templates/user/dashboard.html | 5 + parable/templates/user/pcr.html | 10 ++ parable/templates/user/service.html | 10 ++ parable/templates/user/summary.html | 10 ++ parable/user.py | 24 +++ parable/views.py | 0 praxos/database.py | 8 - praxos/logger.py | 3 +- praxos/models/__init__.py | 0 praxos/models/box.py | 25 --- praxos/models/settings.py | 35 ----- setup.sh | 4 + test.py | 25 ++- 66 files changed, 874 insertions(+), 595 deletions(-) delete mode 100644 db_models/__init__.py delete mode 100644 enigma/engine/database.py delete mode 100644 enigma/models/scorereport.py create mode 100644 enigma_models/__init__.py rename {db_models => enigma_models}/auth.py (100%) create mode 100644 enigma_models/database.py rename {parable => enigma_models}/models/__init__.py (100%) create mode 100644 enigma_models/models/box.py rename {enigma => enigma_models}/models/credlist.py (75%) create mode 100644 enigma_models/models/inject.py create mode 100644 enigma_models/models/scorereport.py rename {enigma => enigma_models}/models/settings.py (58%) rename {enigma => enigma_models}/models/slareport.py (61%) rename {praxos => enigma_models}/models/team.py (50%) rename {praxos => enigma_models}/models/user.py (62%) create mode 100644 parable/admin.py delete mode 100644 parable/database.py delete mode 100644 parable/models/box.py delete mode 100644 parable/models/credlist.py delete mode 100644 parable/models/inject.py delete mode 100644 parable/models/scoreport.py delete mode 100644 parable/models/settings.py delete mode 100644 parable/models/slareport.py delete mode 100644 parable/models/team.py delete mode 100644 parable/models/user.py delete mode 100644 parable/routes.py rename parable/templates/admin/{create_team.html => box.html} (100%) create mode 100644 parable/templates/admin/credlist.html create mode 100644 parable/templates/admin/engine.html create mode 100644 parable/templates/admin/inject.html create mode 100644 parable/templates/admin/settings.html create mode 100644 parable/templates/admin/team.html create mode 100644 parable/templates/admin/user.html create mode 100644 parable/templates/auth/unauthorized.html create mode 100644 parable/templates/index.html create mode 100644 parable/templates/user/dashboard.html create mode 100644 parable/templates/user/pcr.html create mode 100644 parable/templates/user/service.html create mode 100644 parable/templates/user/summary.html create mode 100644 parable/user.py delete mode 100644 parable/views.py delete mode 100644 praxos/database.py delete mode 100644 praxos/models/__init__.py delete mode 100644 praxos/models/box.py delete mode 100644 praxos/models/settings.py 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/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..d3d9288 100644 --- a/docker/enigma/Dockerfile +++ b/docker/enigma/Dockerfile @@ -11,7 +11,7 @@ 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/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/engine/__init__.py b/enigma/engine/__init__.py index d04a27f..70f30b0 100644 --- a/enigma/engine/__init__.py +++ b/enigma/engine/__init__.py @@ -11,5 +11,12 @@ 'port': getenv('POSTGRES_PORT') } +rabbitmq_settings = { + 'user': 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/scoring.py b/enigma/engine/scoring.py index fc2c139..7e221ba 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: @@ -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 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..970899e 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, @@ -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 @@ -222,7 +223,7 @@ def create_credlists(self, credlists: list[Credlist]): 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 @@ -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 100% rename from db_models/auth.py rename to enigma_models/auth.py 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/praxos/models/team.py b/enigma_models/models/team.py similarity index 50% rename from praxos/models/team.py rename to enigma_models/models/team.py index 829b91b..b9299f6 100644 --- a/praxos/models/team.py +++ b/enigma_models/models/team.py @@ -1,10 +1,16 @@ -from sqlmodel import Session, select +from sqlmodel import SQLModel, Field, Session, select -from db_models import RvBTeamDB +from enigma_models.database import db_engine -from praxos.logger import log -from praxos.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): @@ -12,9 +18,11 @@ def __init__(self, name: str, identifier: int, score: int): self.identifier = identifier self.score = score +####################### + # 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) -> bool: - log.debug(f'Adding Team {self.name} to database') + def add_to_db(self): try: with Session(db_engine) as session: session.add( @@ -27,32 +35,23 @@ def add_to_db(self) -> bool: 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 + # 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) -> list: - log.debug(f'Retrieving all teams from database') + def find_all(cls): teams = [] with Session(db_engine) as session: db_teams = session.exec( @@ -68,4 +67,13 @@ def find_all(cls) -> list: score=db_team.score ) ) - return teams \ No newline at end of file + 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 62% rename from praxos/models/user.py rename to enigma_models/models/user.py index 7c20c07..e9d9507 100644 --- a/praxos/models/user.py +++ b/enigma_models/models/user.py @@ -1,13 +1,20 @@ import secrets, string -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: def __init__(self, username: str, identifier: int, permission_level: int, pw_hash: bytes=None): @@ -22,8 +29,10 @@ def create_pw(self, length: int): self.pw_hash = get_hash(password) return 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 +46,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 +61,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 +78,38 @@ def last_identifier(cls): return 0 return last_user.identifier + @classmethod + def find(cls, username: str=None, identifier: int=None): + with Session(db_engine) as session: + 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() + + 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 user 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/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 index e1fe91c..e16b03a 100644 --- a/main/parable/app.py +++ b/main/parable/app.py @@ -1,7 +1,11 @@ -from flask import Flask -app = Flask('parable') -@app.route('/') -def index(): - return "

Hello World

" \ No newline at end of file +from parable.logger import write_log_header +from parable import create_app + +if __name__ == '__main__': + # Initialize logger + write_log_header() + + app = create_app() + app.run(debug=True) \ No newline at end of file diff --git a/main/praxos/main.py b/main/praxos/main.py index e98db8d..a1e95c5 100644 --- a/main/praxos/main.py +++ b/main/praxos/main.py @@ -11,10 +11,10 @@ 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 diff --git a/parable/__init__.py b/parable/__init__.py index 215d15c..2edae8c 100644 --- a/parable/__init__.py +++ b/parable/__init__.py @@ -1,18 +1,29 @@ -import os +from logging.config import dictConfig -from flask import Flask +from flask import Flask, render_template from os import getenv from dotenv import load_dotenv +from parable.logger import log_config + load_dotenv(override=True) -postgres_settings = { - 'user': getenv('POSTGRES_USER'), - 'password': getenv('POSTGRES_PASSWORD'), - 'host': getenv('POSTGRES_HOST'), - 'port': getenv('POSTGRES_PORT') -} +def create_app(): + #dictConfig(log_config) + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + SECRET_KEY=getenv('PARABLE_SECRET_KEY'), + ) + + from parable.auth import bp as auth_bp + app.register_blueprint(auth_bp) + + from parable.user import bp as user_bp + app.register_blueprint(user_bp) + + @app.route('/') + def index(): + return render_template('index.html') -def create_app(test_config=None): - pass \ No newline at end of file + return app \ No newline at end of file diff --git a/parable/admin.py b/parable/admin.py new file mode 100644 index 0000000..3d0dbe8 --- /dev/null +++ b/parable/admin.py @@ -0,0 +1,24 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.auth import admin_required + +bp = Blueprint('admin', __name__, url_prefix='/admin') + +@bp.route('/box', methods=('GET', 'POST')) +@admin_required +def box(): + pass + +@bp.route('/credlist', methods=('GET', 'POST')) +@admin_required +def credlist(): + pass diff --git a/parable/auth.py b/parable/auth.py index e69de29..90f51f8 100644 --- a/parable/auth.py +++ b/parable/auth.py @@ -0,0 +1,86 @@ +import functools + +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from enigma_models.models.user import ParableUser + +bp = Blueprint('auth', __name__, url_prefix='/auth') + +@bp.route('/login', methods=('GET', 'POST')) +def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + + user = ParableUser.find(username=username) + error = None + + if user is None: + error = 'Invalid username' + elif not user.check_pw(password): + error = 'Incorrect password' + + if error is None: + session.clear() + session['user_id'] = user.identifier + return redirect(url_for('user.dashboard')) + + flash(error) + + return render_template('auth/login.html') + +@bp.route('/logout') +def logout(): + session.clear() + return redirect(url_for('index')) + +@bp.route('/unauthorized') +def unauthorized(): + return redirect(url_for('auth.unauthorized')) + +@bp.before_app_request +def load_current_user(): + user_id = session.get('user_id') + + if user_id is None: + g.user = None + else: + g.user = ParableUser.find(identifier=user_id) + +def login_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for('auth.login')) + + return view(**kwargs) + return wrapped_view + +@login_required +def admin_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if not g.user.permission_level == 0: + return redirect(url_for('auth.unauthorized')) + + return view(**kwargs) + return wrapped_view + +@login_required +def gt_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if not g.user.permission_level <= 1: + return redirect(url_for('auth.unauthorized')) + + return view(**kwargs) + return wrapped_view \ 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/logger.py b/parable/logger.py index e69de29..ff39644 100644 --- a/parable/logger.py +++ b/parable/logger.py @@ -0,0 +1,52 @@ +from os import getenv, getcwd +from os.path import join + +from dotenv import load_dotenv + +load_dotenv(override=True) + +#### Creates a universal logger for Enigma + +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' + ]) + +# Creating log config +log_config = { + 'version': 1, + 'formatters': { + 'default': { + 'format': '{asctime} {levelname}: {message}', + 'datefmt': '%Y-%m-%d %H:%M:%S', + 'style': '{', + } + }, + 'handlers': { + 'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }, + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': log_file, + 'mode': 'a', + 'encoding': 'utf-8', + 'formatter': 'default', + 'maxBytes': 50000, + 'backupCount': 5, + } + }, + 'root': { + 'level': log_level, + 'handlers': ['wsgi', 'file', 'stream'] + } +} \ No newline at end of file diff --git a/parable/models/box.py b/parable/models/box.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/models/credlist.py b/parable/models/credlist.py deleted file mode 100644 index e69de29..0000000 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/routes.py b/parable/routes.py deleted file mode 100644 index e69de29..0000000 diff --git a/parable/templates/admin/create_team.html b/parable/templates/admin/box.html similarity index 100% rename from parable/templates/admin/create_team.html rename to parable/templates/admin/box.html diff --git a/parable/templates/admin/credlist.html b/parable/templates/admin/credlist.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/admin/credlist.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file 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..566549b --- /dev/null +++ b/parable/templates/admin/team.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/admin/user.html b/parable/templates/admin/user.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/admin/user.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/auth/login.html b/parable/templates/auth/login.html index 566549b..b7dd5dc 100644 --- a/parable/templates/auth/login.html +++ b/parable/templates/auth/login.html @@ -1,10 +1,15 @@ - - - - - Title - - +{% extends 'base.html' %} - - \ No newline at end of file +{% 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..16f6457 100644 --- a/parable/templates/base.html +++ b/parable/templates/base.html @@ -1,10 +1,25 @@ - - - - Title - - - - - \ No newline at end of file +{% block title %}{% endblock %} + + +
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% 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/templates/user/dashboard.html b/parable/templates/user/dashboard.html new file mode 100644 index 0000000..8a82df2 --- /dev/null +++ b/parable/templates/user/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/user/pcr.html b/parable/templates/user/pcr.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/user/pcr.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/user/service.html b/parable/templates/user/service.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/user/service.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/templates/user/summary.html b/parable/templates/user/summary.html new file mode 100644 index 0000000..566549b --- /dev/null +++ b/parable/templates/user/summary.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/parable/user.py b/parable/user.py new file mode 100644 index 0000000..d3d9c48 --- /dev/null +++ b/parable/user.py @@ -0,0 +1,24 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.auth import login_required + +bp = Blueprint('user', __name__, url_prefix='/user') + +@bp.route('/dashboard', methods='GET') +@login_required +def dashboard(): + return render_template('user/dashboard.html') + +@bp.route('/summary', methods='GET') +@login_required +def summary(): + pass \ 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/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/setup.sh b/setup.sh index e69de29..688a675 100644 --- a/setup.sh +++ b/setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +apt install git -y + diff --git a/test.py b/test.py index 3d46092..fa02ba4 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,18 +52,30 @@ 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() Settings(first_octets='10.10', sla_requirement=2) +print([(user[0].username, user[1]) for user in users]) + while True: cmd = input('Enter command: ') with RabbitMQ() as rabbit: From df5014b10b4073310467c37dfb03ff1ccb20be33 Mon Sep 17 00:00:00 2001 From: EntangledLabs Date: Tue, 24 Dec 2024 10:23:40 -0800 Subject: [PATCH 02/10] created individual files for model views --- enigma/checks/__init__.py | 2 +- enigma/engine/__init__.py | 4 +- enigma/engine/rabbitmq.py | 2 +- enigma/engine/scoring.py | 12 ++--- enigma/models/team.py | 8 +-- enigma_models/__init__.py | 2 +- enigma_models/database.py | 2 +- enigma_models/models/team.py | 2 +- enigma_models/models/user.py | 11 +++- main/parable/app.py | 7 +-- main/praxos/main.py | 20 +++---- parable/__init__.py | 26 ++++++++-- parable/admin.py | 24 --------- parable/admin/__init__.py | 3 ++ parable/admin/box.py | 20 +++++++ parable/admin/credlist.py | 12 +++++ parable/admin/engine.py | 12 +++++ parable/admin/inject.py | 12 +++++ parable/admin/settings.py | 12 +++++ parable/admin/team.py | 12 +++++ parable/admin/user.py | 52 +++++++++++++++++++ parable/auth.py | 2 +- parable/competitor/__init__.py | 3 ++ parable/competitor/dashboard.py | 21 ++++++++ parable/rabbitmq.py | 51 ++++++++++++++++++ parable/templates/admin/box.html | 13 ++--- parable/templates/admin/team.html | 28 ++++++---- parable/templates/admin/user.html | 10 ---- .../{user => competitor}/dashboard.html | 0 .../templates/{user => competitor}/pcr.html | 0 .../{user => competitor}/service.html | 0 .../{user => competitor}/summary.html | 0 parable/user.py | 24 --------- praxos/__init__.py | 2 +- 34 files changed, 296 insertions(+), 115 deletions(-) delete mode 100644 parable/admin.py create mode 100644 parable/admin/__init__.py create mode 100644 parable/admin/box.py create mode 100644 parable/admin/credlist.py create mode 100644 parable/admin/engine.py create mode 100644 parable/admin/inject.py create mode 100644 parable/admin/settings.py create mode 100644 parable/admin/team.py create mode 100644 parable/admin/user.py create mode 100644 parable/competitor/__init__.py create mode 100644 parable/competitor/dashboard.py create mode 100644 parable/rabbitmq.py delete mode 100644 parable/templates/admin/user.html rename parable/templates/{user => competitor}/dashboard.html (100%) rename parable/templates/{user => competitor}/pcr.html (100%) rename parable/templates/{user => competitor}/service.html (100%) rename parable/templates/{user => competitor}/summary.html (100%) delete mode 100644 parable/user.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 70f30b0..eeeb312 100644 --- a/enigma/engine/__init__.py +++ b/enigma/engine/__init__.py @@ -5,14 +5,14 @@ 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 = { - 'user': getenv('RABBITMQ_DEFAULT_USER'), + 'competitor': getenv('RABBITMQ_DEFAULT_USER'), 'password': getenv('RABBITMQ_DEFAULT_PASSWORD'), 'host': getenv('RABBITMQ_HOST'), 'port': 5672 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 7e221ba..3b90038 100644 --- a/enigma/engine/scoring.py +++ b/enigma/engine/scoring.py @@ -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 @@ -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/models/team.py b/enigma/models/team.py index 970899e..f57d33c 100644 --- a/enigma/models/team.py +++ b/enigma/models/team.py @@ -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): @@ -216,7 +216,7 @@ 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: @@ -226,7 +226,7 @@ def create_credlists(self, credlists: list[Credlist]): 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}') diff --git a/enigma_models/__init__.py b/enigma_models/__init__.py index 4d1de2b..fc8b0ba 100644 --- a/enigma_models/__init__.py +++ b/enigma_models/__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/enigma_models/database.py b/enigma_models/database.py index b27ad93..381b9c0 100644 --- a/enigma_models/database.py +++ b/enigma_models/database.py @@ -3,7 +3,7 @@ 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', + f'postgresql+psycopg://{postgres_settings['competitor']}:{postgres_settings['password']}@{postgres_settings['host']}:{postgres_settings['port']}/enigma', echo=False ) diff --git a/enigma_models/models/team.py b/enigma_models/models/team.py index b9299f6..b7822c3 100644 --- a/enigma_models/models/team.py +++ b/enigma_models/models/team.py @@ -21,7 +21,7 @@ def __init__(self, name: str, identifier: int, score: int): ####################### # DB fetch/add - # Tries to add the team object to the DB. If exists, it will return False, else True + # 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: diff --git a/enigma_models/models/user.py b/enigma_models/models/user.py index e9d9507..6192c0a 100644 --- a/enigma_models/models/user.py +++ b/enigma_models/models/user.py @@ -1,4 +1,5 @@ import secrets, string +from enum import Enum from sqlmodel import SQLModel, Field, Session, select @@ -17,6 +18,11 @@ class ParableUserDB(SQLModel, table=True): # 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 @@ -29,6 +35,9 @@ 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) @@ -107,7 +116,7 @@ def find(cls, username: str=None, identifier: int=None): ) return None - # Fetches all Parable user from the DB + # Fetches all Parable competitor from the DB @classmethod def find_all(cls) -> list: users = [] diff --git a/main/parable/app.py b/main/parable/app.py index e16b03a..4995e60 100644 --- a/main/parable/app.py +++ b/main/parable/app.py @@ -1,11 +1,8 @@ - - -from parable.logger import write_log_header from parable import create_app if __name__ == '__main__': - # Initialize logger - write_log_header() + + # Run server app = create_app() app.run(debug=True) \ No newline at end of file diff --git a/main/praxos/main.py b/main/praxos/main.py index a1e95c5..b42f355 100644 --- a/main/praxos/main.py +++ b/main/praxos/main.py @@ -18,7 +18,7 @@ ############### # TODO: Add "praxos event" creation -# TODO: Add team creds +# TODO: Add competitor creds load_dotenv(override=True) @@ -122,7 +122,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 +202,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 +219,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 +248,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 +274,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 +283,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 2edae8c..eb9c17a 100644 --- a/parable/__init__.py +++ b/parable/__init__.py @@ -5,23 +5,41 @@ from os import getenv from dotenv import load_dotenv -from parable.logger import log_config +from enigma_models.models.user import ParableUser + +from parable.logger import log_config, write_log_header load_dotenv(override=True) def create_app(): #dictConfig(log_config) + + # Initialize logger + write_log_header() + + # Create admin competitor with username 'admin' and password 'enigma' + admin = ParableUser( + username='admin', + identifier=0, + permission_level=0 + ) + admin.set_pw('enigma') + print(admin.add_to_db()) + app = Flask(__name__, instance_relative_config=True) app.config.from_mapping( SECRET_KEY=getenv('PARABLE_SECRET_KEY'), ) + from parable.competitor import bp as user_bp + app.register_blueprint(user_bp) + + from parable.admin import bp as admin_bp + app.register_blueprint(admin_bp) + from parable.auth import bp as auth_bp app.register_blueprint(auth_bp) - from parable.user import bp as user_bp - app.register_blueprint(user_bp) - @app.route('/') def index(): return render_template('index.html') diff --git a/parable/admin.py b/parable/admin.py deleted file mode 100644 index 3d0dbe8..0000000 --- a/parable/admin.py +++ /dev/null @@ -1,24 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.auth import admin_required - -bp = Blueprint('admin', __name__, url_prefix='/admin') - -@bp.route('/box', methods=('GET', 'POST')) -@admin_required -def box(): - pass - -@bp.route('/credlist', methods=('GET', 'POST')) -@admin_required -def credlist(): - pass diff --git a/parable/admin/__init__.py b/parable/admin/__init__.py new file mode 100644 index 0000000..d09ec61 --- /dev/null +++ b/parable/admin/__init__.py @@ -0,0 +1,3 @@ +from flask import Blueprint + +bp = Blueprint('admin', __name__, url_prefix='/admin') \ No newline at end of file diff --git a/parable/admin/box.py b/parable/admin/box.py new file mode 100644 index 0000000..2a51449 --- /dev/null +++ b/parable/admin/box.py @@ -0,0 +1,20 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.admin import bp + +@bp.route('/box', methods=['GET', 'POST']) +def box(): + pass + +@bp.route('/box', methods='DELETE') +def box_delete(): + pass \ No newline at end of file diff --git a/parable/admin/credlist.py b/parable/admin/credlist.py new file mode 100644 index 0000000..0982216 --- /dev/null +++ b/parable/admin/credlist.py @@ -0,0 +1,12 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.admin import bp diff --git a/parable/admin/engine.py b/parable/admin/engine.py new file mode 100644 index 0000000..0982216 --- /dev/null +++ b/parable/admin/engine.py @@ -0,0 +1,12 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.admin import bp diff --git a/parable/admin/inject.py b/parable/admin/inject.py new file mode 100644 index 0000000..0982216 --- /dev/null +++ b/parable/admin/inject.py @@ -0,0 +1,12 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.admin import bp diff --git a/parable/admin/settings.py b/parable/admin/settings.py new file mode 100644 index 0000000..0982216 --- /dev/null +++ b/parable/admin/settings.py @@ -0,0 +1,12 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.admin import bp diff --git a/parable/admin/team.py b/parable/admin/team.py new file mode 100644 index 0000000..0982216 --- /dev/null +++ b/parable/admin/team.py @@ -0,0 +1,12 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.admin import bp diff --git a/parable/admin/user.py b/parable/admin/user.py new file mode 100644 index 0000000..c58022f --- /dev/null +++ b/parable/admin/user.py @@ -0,0 +1,52 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from enigma_models.models.user import ParableUser + +from parable.admin import bp + +@bp.route('/team', methods=['GET', 'POST']) +def user(): + if request.method == 'POST': + pass + + return render_template('admin/team.html') + +@bp.route('/team/create', methods='POST') +def user_add(): + username = request.form['username'] + identifier = ParableUser.last_identifier() + permission = ParableUser.Permission[request.form['permission']].value + password = request.form['password'] + error = None + + if not username: + error = 'Username is required.' + elif not password: + error = 'Password is required.' + + if error is None: + user = ParableUser( + username=username, + identifier=identifier, + permission_level=permission + ) + user.set_pw(password) + user.add_to_db() + + flash(error) + return render_template('admin/team.html') + + +@bp.route('/team/', methods=['GET', 'PUT', 'DELETE']) +def user_modify(id): + if request.method == 'GET': + pass \ No newline at end of file diff --git a/parable/auth.py b/parable/auth.py index 90f51f8..094a944 100644 --- a/parable/auth.py +++ b/parable/auth.py @@ -32,7 +32,7 @@ def login(): if error is None: session.clear() session['user_id'] = user.identifier - return redirect(url_for('user.dashboard')) + return redirect(url_for('index')) flash(error) diff --git a/parable/competitor/__init__.py b/parable/competitor/__init__.py new file mode 100644 index 0000000..1cd9b8f --- /dev/null +++ b/parable/competitor/__init__.py @@ -0,0 +1,3 @@ +from flask import Blueprint + +bp = Blueprint('competitor', __name__, url_prefix='/competitor') \ No newline at end of file diff --git a/parable/competitor/dashboard.py b/parable/competitor/dashboard.py new file mode 100644 index 0000000..518a3e2 --- /dev/null +++ b/parable/competitor/dashboard.py @@ -0,0 +1,21 @@ +from flask import ( + Blueprint, + flash, + render_template, + g, + redirect, + request, + session, + url_for +) + +from parable.auth import login_required +from parable.competitor import bp + +@bp.route('/dashboard', methods=['GET', 'POST']) +@login_required +def dashboard(): + if request.method == 'POST': + pass + + return render_template('competitor/dashboard.html') \ No newline at end of file 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/templates/admin/box.html b/parable/templates/admin/box.html index 566549b..083b709 100644 --- a/parable/templates/admin/box.html +++ b/parable/templates/admin/box.html @@ -1,10 +1,5 @@ - - - - - Title - - +{% extends 'base.html' %} - - \ No newline at end of file +{% block header %} +

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

+{% endblock %} \ No newline at end of file diff --git a/parable/templates/admin/team.html b/parable/templates/admin/team.html index 566549b..e108ad2 100644 --- a/parable/templates/admin/team.html +++ b/parable/templates/admin/team.html @@ -1,10 +1,20 @@ - - - - - Title - - +{% extends 'base.html' %} - - \ No newline at end of file +{% block header %} +

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

+{% endblock %} + +{% block content %} +
+ + + + + + + + + + +
+{% endblock %} \ No newline at end of file diff --git a/parable/templates/admin/user.html b/parable/templates/admin/user.html deleted file mode 100644 index 566549b..0000000 --- a/parable/templates/admin/user.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Title - - - - - \ No newline at end of file diff --git a/parable/templates/user/dashboard.html b/parable/templates/competitor/dashboard.html similarity index 100% rename from parable/templates/user/dashboard.html rename to parable/templates/competitor/dashboard.html diff --git a/parable/templates/user/pcr.html b/parable/templates/competitor/pcr.html similarity index 100% rename from parable/templates/user/pcr.html rename to parable/templates/competitor/pcr.html diff --git a/parable/templates/user/service.html b/parable/templates/competitor/service.html similarity index 100% rename from parable/templates/user/service.html rename to parable/templates/competitor/service.html diff --git a/parable/templates/user/summary.html b/parable/templates/competitor/summary.html similarity index 100% rename from parable/templates/user/summary.html rename to parable/templates/competitor/summary.html diff --git a/parable/user.py b/parable/user.py deleted file mode 100644 index d3d9c48..0000000 --- a/parable/user.py +++ /dev/null @@ -1,24 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.auth import login_required - -bp = Blueprint('user', __name__, url_prefix='/user') - -@bp.route('/dashboard', methods='GET') -@login_required -def dashboard(): - return render_template('user/dashboard.html') - -@bp.route('/summary', methods='GET') -@login_required -def summary(): - pass \ No newline at end of file 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') From c996020f07f99ed0306ce10959b3d89bf5ca362d Mon Sep 17 00:00:00 2001 From: EntangledLabs Date: Wed, 25 Dec 2024 00:44:34 -0800 Subject: [PATCH 03/10] created starlette app, router helper classes --- main/parable/app.py | 8 -- main/parable/main.py | 47 ++++++++++ parable/__init__.py | 50 ++-------- parable/admin/__init__.py | 3 - parable/admin/box.py | 20 ---- parable/admin/credlist.py | 12 --- parable/admin/engine.py | 12 --- parable/admin/inject.py | 12 --- parable/admin/settings.py | 12 --- parable/admin/team.py | 12 --- parable/admin/user.py | 52 ----------- parable/auth.py | 86 ------------------ parable/competitor/__init__.py | 3 - parable/competitor/dashboard.py | 21 ----- parable/logger.py | 58 ++++++------ parable/requirements.txt | Bin 0 -> 984 bytes parable/route.py | 59 ++++++++++++ parable/static/{style.css => css/parable.css} | 0 parable/static/scores/team1_final.csv | 7 ++ parable/static/scores/team2_final.csv | 7 ++ parable/static/scores/team3_final.csv | 7 ++ parable/static/scores/team4_final.csv | 7 ++ parable/static/scores/team5_final.csv | 7 ++ .../static/scss/parable.scss | 0 parable/templates/base.html | 12 +-- test.py | 59 +++++++++++- 26 files changed, 233 insertions(+), 340 deletions(-) delete mode 100644 main/parable/app.py create mode 100644 main/parable/main.py delete mode 100644 parable/admin/__init__.py delete mode 100644 parable/admin/box.py delete mode 100644 parable/admin/credlist.py delete mode 100644 parable/admin/engine.py delete mode 100644 parable/admin/inject.py delete mode 100644 parable/admin/settings.py delete mode 100644 parable/admin/team.py delete mode 100644 parable/admin/user.py delete mode 100644 parable/competitor/__init__.py delete mode 100644 parable/competitor/dashboard.py create mode 100644 parable/requirements.txt create mode 100644 parable/route.py rename parable/static/{style.css => css/parable.css} (100%) create mode 100644 parable/static/scores/team1_final.csv create mode 100644 parable/static/scores/team2_final.csv create mode 100644 parable/static/scores/team3_final.csv create mode 100644 parable/static/scores/team4_final.csv create mode 100644 parable/static/scores/team5_final.csv rename static/.gitkeep => parable/static/scss/parable.scss (100%) diff --git a/main/parable/app.py b/main/parable/app.py deleted file mode 100644 index 4995e60..0000000 --- a/main/parable/app.py +++ /dev/null @@ -1,8 +0,0 @@ -from parable import create_app - -if __name__ == '__main__': - - - # Run server - app = create_app() - app.run(debug=True) \ No newline at end of file diff --git a/main/parable/main.py b/main/parable/main.py new file mode 100644 index 0000000..c189077 --- /dev/null +++ b/main/parable/main.py @@ -0,0 +1,47 @@ +from contextlib import asynccontextmanager +import uvicorn + +from starlette.applications import Starlette +from starlette.responses import JSONResponse +from starlette.requests import Request + +from parable.logger import log_config, write_log_header +from parable.route import Route, StaticRouter +from parable import templates + +# Lifespan handler +@asynccontextmanager +async def lifespan(app): + print(app.router.routes) + write_log_header() + yield + +# Index route +index_routes = Route() + +@index_routes.route("/", methods=["GET"]) +async def index(request): + template = "index.html" + context = {"request": request} + return templates.TemplateResponse(template, context) + +# All routes +app_routes = [ + index_routes, + StaticRouter() +] + +# Application creation +app = Starlette( + debug=True, + lifespan=lifespan, + routes=Route.get_routes(app_routes) +) + +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/parable/__init__.py b/parable/__init__.py index eb9c17a..7aa2527 100644 --- a/parable/__init__.py +++ b/parable/__init__.py @@ -1,47 +1,9 @@ -from logging.config import dictConfig +from os import getcwd +from os.path import join -from flask import Flask, render_template +from starlette.templating import Jinja2Templates +from starlette.staticfiles import StaticFiles -from os import getenv -from dotenv import load_dotenv +templates = Jinja2Templates(directory=join(getcwd(), 'parable', 'templates')) -from enigma_models.models.user import ParableUser - -from parable.logger import log_config, write_log_header - -load_dotenv(override=True) - -def create_app(): - #dictConfig(log_config) - - # Initialize logger - write_log_header() - - # Create admin competitor with username 'admin' and password 'enigma' - admin = ParableUser( - username='admin', - identifier=0, - permission_level=0 - ) - admin.set_pw('enigma') - print(admin.add_to_db()) - - app = Flask(__name__, instance_relative_config=True) - app.config.from_mapping( - SECRET_KEY=getenv('PARABLE_SECRET_KEY'), - ) - - from parable.competitor import bp as user_bp - app.register_blueprint(user_bp) - - from parable.admin import bp as admin_bp - app.register_blueprint(admin_bp) - - from parable.auth import bp as auth_bp - app.register_blueprint(auth_bp) - - @app.route('/') - def index(): - return render_template('index.html') - - return app \ No newline at end of file +static = StaticFiles(directory=join(getcwd(), 'parable', 'static')) \ No newline at end of file diff --git a/parable/admin/__init__.py b/parable/admin/__init__.py deleted file mode 100644 index d09ec61..0000000 --- a/parable/admin/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from flask import Blueprint - -bp = Blueprint('admin', __name__, url_prefix='/admin') \ No newline at end of file diff --git a/parable/admin/box.py b/parable/admin/box.py deleted file mode 100644 index 2a51449..0000000 --- a/parable/admin/box.py +++ /dev/null @@ -1,20 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.admin import bp - -@bp.route('/box', methods=['GET', 'POST']) -def box(): - pass - -@bp.route('/box', methods='DELETE') -def box_delete(): - pass \ No newline at end of file diff --git a/parable/admin/credlist.py b/parable/admin/credlist.py deleted file mode 100644 index 0982216..0000000 --- a/parable/admin/credlist.py +++ /dev/null @@ -1,12 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.admin import bp diff --git a/parable/admin/engine.py b/parable/admin/engine.py deleted file mode 100644 index 0982216..0000000 --- a/parable/admin/engine.py +++ /dev/null @@ -1,12 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.admin import bp diff --git a/parable/admin/inject.py b/parable/admin/inject.py deleted file mode 100644 index 0982216..0000000 --- a/parable/admin/inject.py +++ /dev/null @@ -1,12 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.admin import bp diff --git a/parable/admin/settings.py b/parable/admin/settings.py deleted file mode 100644 index 0982216..0000000 --- a/parable/admin/settings.py +++ /dev/null @@ -1,12 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.admin import bp diff --git a/parable/admin/team.py b/parable/admin/team.py deleted file mode 100644 index 0982216..0000000 --- a/parable/admin/team.py +++ /dev/null @@ -1,12 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.admin import bp diff --git a/parable/admin/user.py b/parable/admin/user.py deleted file mode 100644 index c58022f..0000000 --- a/parable/admin/user.py +++ /dev/null @@ -1,52 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from enigma_models.models.user import ParableUser - -from parable.admin import bp - -@bp.route('/team', methods=['GET', 'POST']) -def user(): - if request.method == 'POST': - pass - - return render_template('admin/team.html') - -@bp.route('/team/create', methods='POST') -def user_add(): - username = request.form['username'] - identifier = ParableUser.last_identifier() - permission = ParableUser.Permission[request.form['permission']].value - password = request.form['password'] - error = None - - if not username: - error = 'Username is required.' - elif not password: - error = 'Password is required.' - - if error is None: - user = ParableUser( - username=username, - identifier=identifier, - permission_level=permission - ) - user.set_pw(password) - user.add_to_db() - - flash(error) - return render_template('admin/team.html') - - -@bp.route('/team/', methods=['GET', 'PUT', 'DELETE']) -def user_modify(id): - if request.method == 'GET': - pass \ No newline at end of file diff --git a/parable/auth.py b/parable/auth.py index 094a944..e69de29 100644 --- a/parable/auth.py +++ b/parable/auth.py @@ -1,86 +0,0 @@ -import functools - -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from enigma_models.models.user import ParableUser - -bp = Blueprint('auth', __name__, url_prefix='/auth') - -@bp.route('/login', methods=('GET', 'POST')) -def login(): - if request.method == 'POST': - username = request.form['username'] - password = request.form['password'] - - user = ParableUser.find(username=username) - error = None - - if user is None: - error = 'Invalid username' - elif not user.check_pw(password): - error = 'Incorrect password' - - if error is None: - session.clear() - session['user_id'] = user.identifier - return redirect(url_for('index')) - - flash(error) - - return render_template('auth/login.html') - -@bp.route('/logout') -def logout(): - session.clear() - return redirect(url_for('index')) - -@bp.route('/unauthorized') -def unauthorized(): - return redirect(url_for('auth.unauthorized')) - -@bp.before_app_request -def load_current_user(): - user_id = session.get('user_id') - - if user_id is None: - g.user = None - else: - g.user = ParableUser.find(identifier=user_id) - -def login_required(view): - @functools.wraps(view) - def wrapped_view(**kwargs): - if g.user is None: - return redirect(url_for('auth.login')) - - return view(**kwargs) - return wrapped_view - -@login_required -def admin_required(view): - @functools.wraps(view) - def wrapped_view(**kwargs): - if not g.user.permission_level == 0: - return redirect(url_for('auth.unauthorized')) - - return view(**kwargs) - return wrapped_view - -@login_required -def gt_required(view): - @functools.wraps(view) - def wrapped_view(**kwargs): - if not g.user.permission_level <= 1: - return redirect(url_for('auth.unauthorized')) - - return view(**kwargs) - return wrapped_view \ No newline at end of file diff --git a/parable/competitor/__init__.py b/parable/competitor/__init__.py deleted file mode 100644 index 1cd9b8f..0000000 --- a/parable/competitor/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from flask import Blueprint - -bp = Blueprint('competitor', __name__, url_prefix='/competitor') \ No newline at end of file diff --git a/parable/competitor/dashboard.py b/parable/competitor/dashboard.py deleted file mode 100644 index 518a3e2..0000000 --- a/parable/competitor/dashboard.py +++ /dev/null @@ -1,21 +0,0 @@ -from flask import ( - Blueprint, - flash, - render_template, - g, - redirect, - request, - session, - url_for -) - -from parable.auth import login_required -from parable.competitor import bp - -@bp.route('/dashboard', methods=['GET', 'POST']) -@login_required -def dashboard(): - if request.method == 'POST': - pass - - return render_template('competitor/dashboard.html') \ No newline at end of file diff --git a/parable/logger.py b/parable/logger.py index ff39644..60060c6 100644 --- a/parable/logger.py +++ b/parable/logger.py @@ -3,9 +3,13 @@ from dotenv import load_dotenv +from uvicorn.config import LOGGING_CONFIG + load_dotenv(override=True) -#### Creates a universal logger for Enigma +#### Creates a universal logger for Parable + +log_config = LOGGING_CONFIG log_level = getenv('LOG_LEVEL') logs_path = join(getcwd(), 'logs') @@ -19,34 +23,26 @@ def write_log_header(): '++++==== Parable Web Interface Log ====++++\n' ]) -# Creating log config -log_config = { - 'version': 1, - 'formatters': { - 'default': { - 'format': '{asctime} {levelname}: {message}', - 'datefmt': '%Y-%m-%d %H:%M:%S', - 'style': '{', - } - }, - 'handlers': { - 'wsgi': { - 'class': 'logging.StreamHandler', - 'stream': 'ext://flask.logging.wsgi_errors_stream', - 'formatter': 'default' - }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': log_file, - 'mode': 'a', - 'encoding': 'utf-8', - 'formatter': 'default', - 'maxBytes': 50000, - 'backupCount': 5, - } - }, - 'root': { - 'level': log_level, - 'handlers': ['wsgi', 'file', 'stream'] +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 } -} \ No newline at end of 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/requirements.txt b/parable/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..0352cb5a3beea1c1456eb5d3788f159c328c0453 GIT binary patch literal 984 zcmZuwO;5s55S+7#KZS-CMJ^t=8c8&qJP|A)mGW9?G1MQg&g`~)7(-rKIP6GkP*iusHX z^Icg|yP@YO8=^v_UTB9@bYI<-)X_z%`k;+G$9s4+HLBVC%Wm49G_%wg zJ|$`KNtKNBzMV6>@YZ|Rc69n0I<=<0z&%> {% block title %}{% endblock %} - +
{% block header %}{% endblock %}
- {% for message in get_flashed_messages() %} -
{{ message }}
- {% endfor %} {% block content %}{% endblock %}
\ No newline at end of file diff --git a/test.py b/test.py index fa02ba4..f88513a 100644 --- a/test.py +++ b/test.py @@ -1,4 +1,4 @@ -import json, csv +"""import json, csv from os.path import join, isfile, splitext from os import listdir @@ -83,4 +83,59 @@ exchange='enigma', routing_key='enigma.engine.cmd', body=cmd - ) \ No newline at end of file + )""" + +from typing import override + +class Route: + + def __init__(self): + self.routes = [] + + def route(self, path: str, methods: list): + def decorator(func): + self.routes.append( + ( + path, + func, + methods + ) + ) + return decorator + + def build_routes(self): + return self.routes + + @classmethod + def get_routes(cls, routes_list: list): + all_routes = [] + for route in routes_list: + if isinstance(route, Route): + all_routes.append(route.build_routes()) + return all_routes + +class Router(Route): + + def __init__(self, name: str): + super().__init__() + self.name = name + self.path = f'/{name}' + + @override + def build_routes(self): + return ( + self.path, + self.routes + ) + +routes = Route() + +@routes.route('/', methods=['GET', 'POST']) +def index(request): + print('success') + print(request) + +if __name__ == '__main__': + print(routes.routes) + routes.routes[0][1]('test') + print(routes.routes) \ No newline at end of file From 2f7a9bf2eb68b8c52a46e9efe1c21f9121400b46 Mon Sep 17 00:00:00 2001 From: EntangledLabs Date: Sat, 4 Jan 2025 15:57:03 -0800 Subject: [PATCH 04/10] stuck at route finding error --- main/parable/main.py | 45 ++++++++++----- parable/__init__.py | 10 +++- parable/auth.py | 95 +++++++++++++++++++++++++++++++ parable/exceptions.py | 4 ++ parable/models/__init__.py | 0 parable/models/user.py | 1 + parable/routes.py | 16 ++++++ parable/templates/auth/login.html | 32 ++++++++--- parable/templates/base.html | 4 ++ 9 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 parable/exceptions.py create mode 100644 parable/models/__init__.py create mode 100644 parable/models/user.py create mode 100644 parable/routes.py diff --git a/main/parable/main.py b/main/parable/main.py index c189077..a3c6e04 100644 --- a/main/parable/main.py +++ b/main/parable/main.py @@ -2,40 +2,55 @@ import uvicorn from starlette.applications import Starlette -from starlette.responses import JSONResponse -from starlette.requests import Request +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 from parable.logger import log_config, write_log_header from parable.route import Route, StaticRouter -from parable import templates +from parable.routes import index_routes, admin_router, user_router +from parable.auth import ParableAuthBackend +from parable import secret_key + # Lifespan handler @asynccontextmanager async def lifespan(app): - print(app.router.routes) + for route in app.router.routes: + print(route) + if isinstance(route, Mount): + if isinstance(route.app, StarletteRouter): + for mount_route in route.app.routes: + print(mount_route) + print(mount_route.url_path_for(mount_route.name)) + else: + print(route.url_path_for(route.name)) + print(app.router) + print(app.router.url_path_for('token')) write_log_header() yield -# Index route -index_routes = Route() - -@index_routes.route("/", methods=["GET"]) -async def index(request): - template = "index.html" - context = {"request": request} - return templates.TemplateResponse(template, context) - -# All routes +# Routes app_routes = [ index_routes, + admin_router, + user_router, StaticRouter() ] +# Middleware +middleware = [ + #Middleware(AuthenticationMiddleware, backend=ParableAuthBackend), + #Middleware(SessionMiddleware, secret_key=secret_key) +] + # Application creation app = Starlette( debug=True, lifespan=lifespan, - routes=Route.get_routes(app_routes) + routes=Route.get_routes(app_routes), + middleware=middleware ) if __name__ == '__main__': diff --git a/parable/__init__.py b/parable/__init__.py index 7aa2527..f68a284 100644 --- a/parable/__init__.py +++ b/parable/__init__.py @@ -1,9 +1,15 @@ -from os import getcwd +from os import getcwd, getenv from os.path import join +from dotenv import load_dotenv + from starlette.templating import Jinja2Templates from starlette.staticfiles import StaticFiles +load_dotenv(override=True) + templates = Jinja2Templates(directory=join(getcwd(), 'parable', 'templates')) -static = StaticFiles(directory=join(getcwd(), 'parable', 'static')) \ No newline at end of file +static = StaticFiles(directory=join(getcwd(), 'parable', 'static')) + +secret_key = getenv('PARABLE_SECRET_KEY') \ No newline at end of file diff --git a/parable/auth.py b/parable/auth.py index e69de29..2d30e29 100644 --- a/parable/auth.py +++ b/parable/auth.py @@ -0,0 +1,95 @@ +from datetime import datetime, timedelta, timezone + +import binascii +import jwt +from jwt.exceptions import InvalidTokenError + +from pydantic import BaseModel, Field + +from starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError +from starlette.exceptions import HTTPException + +from enigma_models.models.user import ParableUser as ParableUser +from enigma_models.auth import get_hash, get_hash_from_salted_hash, get_hash_from_salt, verify_hash + +from parable.route import Router, Route +from parable.routes import index_routes +from parable import templates, secret_key + +# User auth +class Token(BaseModel): + access_token: str + token_type: str + +class TokenData(BaseModel): + username: str | None = None + +def authenticate_user(username: str, password: str): + user = ParableUser.find(username=username) + if not user: + return False + if not verify_hash(password, user.pwhash): + return False + return user + +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') + +async def get_current_user(token: str): + credentials_exception = HTTPException( + status_code=401, + detail='Credentials could not be validated', + headers={"WWW-Authenticate": "Bearer"} + ) + try: + payload = jwt.decode(token, secret_key, algorithms=['HS256']) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + token_data = TokenData(username=username) + except InvalidTokenError: + raise credentials_exception + user = ParableUser.find(username=username) + if user is None: + raise credentials_exception + return user + +class ParableAuthBackend(AuthenticationBackend): + async def authenticate(self, conn): + if "Authorization" not in conn.headers: + return + + auth = conn.headers["Authorization"] + try: + scheme, credentials = auth.split() + if scheme.lower() != "basic": + return + except (ValueError, UnicodeDecodeError, binascii.Error) as exc: + raise AuthenticationError('Invalid auth credentials') + +@index_routes.route('/login', methods=['GET']) +async def login(request): + template = 'auth/login.html' + context = {'request': request} + return templates.TemplateResponse(request, template) + +@index_routes.route('/logout', methods=['GET']) +async def logout(request): + template = 'logout.html' + context = {'request': request} + return templates.TemplateResponse(request, template) + +@index_routes.route('/token', methods=['POST']) +async def get_token(request): + print(request) + +def login_required(view): + pass \ 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/models/__init__.py b/parable/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/parable/models/user.py b/parable/models/user.py new file mode 100644 index 0000000..67c6d2f --- /dev/null +++ b/parable/models/user.py @@ -0,0 +1 @@ +from enigma_models.models.user import ParableUser \ No newline at end of file diff --git a/parable/routes.py b/parable/routes.py new file mode 100644 index 0000000..9b211e0 --- /dev/null +++ b/parable/routes.py @@ -0,0 +1,16 @@ +from starlette.authentication import requires + +from parable.route import Router, Route +from parable import templates + +user_router = Router(name='user') +admin_router = Router(name='admin') + +# Index route +index_routes = Route() + +@index_routes.route("/", methods=["GET"]) +async def index(request): + template = "index.html" + context = {"request": request} + return templates.TemplateResponse(template, context) \ No newline at end of file diff --git a/parable/templates/auth/login.html b/parable/templates/auth/login.html index b7dd5dc..0921679 100644 --- a/parable/templates/auth/login.html +++ b/parable/templates/auth/login.html @@ -5,11 +5,29 @@

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

{% endblock %} {% block content %} -
- - - - - -
+
+ + + + + +
+ + {% endblock %} \ No newline at end of file diff --git a/parable/templates/base.html b/parable/templates/base.html index 5365163..85dc9ba 100644 --- a/parable/templates/base.html +++ b/parable/templates/base.html @@ -1,4 +1,8 @@ + + + + {% block title %}{% endblock %}