Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
LOG_LEVEL: DEBUG
DATABASE_URL: ${{ secrets.DATABASE_URL }}

services:
postgres:
Expand Down Expand Up @@ -49,8 +50,8 @@ jobs:
sleep 2
done

- name: Run unit tests
run: pytest -m "unit"
- name: Run tests
run: pytest

- name: Run integration and migration tests
if: github.ref == 'refs/heads/qa'
Expand Down
4 changes: 2 additions & 2 deletions alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ prepend_sys_path = .

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
revision_environment = true

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
Expand Down Expand Up @@ -84,7 +84,7 @@ path_separator = os
# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = driver://user:pass@localhost/dbname
#sqlalchemy.url = driver://user:pass@localhost/dbname


[post_write_hooks]
Expand Down
5 changes: 3 additions & 2 deletions alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from app.db import models # noqa: F401
from app.core.config import settings


setup_logging()
logger = logging.getLogger("rulecheck")

setup_logging()
logger = logging.getLogger("rulecheck")

def run_migrations_online():
logger.info("Starting online migration...")
Expand Down Expand Up @@ -57,7 +58,7 @@ def run_migrations_offline():

script = ScriptDirectory.from_config(context.config)
current_head = script.get_current_head()
logger.info(f"Offline migration. Head version: {current_head}")
logger.debug(f"Offline migration. Head version: {current_head}")
logger.info("Finished offline migration.")
except Exception as e:
logger.exception("Error during offline migration")
Expand Down
43 changes: 39 additions & 4 deletions app/api/action_submissions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from requests import Session
from app.core.logging_config import logger
from app.db.database import get_db
from app.db.models import ActionSubmission
from app.schemas.action_submission.create_action_submission import ActionSubmissionBase
from app.utils.admin_check import admin_lock

router = APIRouter()


@router.get("/", tags=["action_submissions"])
def list_rules():
logger.info("Testing Action_Submission API call.")
return {"message": "Action Submissions endpoint ready"}
def health_check_action_submissions():
logger.info("Testing Action Submissions API call.")
return {"message": "Action Submissions Event endpoint ready"}


@router.get(
"/get_action_submission/{action_submission_id}", tags=["action_submissions"]
)
def retrieve_request(action_submission_id: int, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
action_submission = (
db.query(ActionSubmission)
.filter(ActionSubmission.id == action_submission_id)
.first()
)
if not action_submission:
raise HTTPException(
status_code=404,
detail=f"Action Submission with ID {action_submission_id} not found",
)
return action_submission


@router.post("/create_action_submission", tags=["action_submissions"])
def decide(submission: ActionSubmissionBase, db: Session = Depends(get_db)):
db_submission = ActionSubmission(**submission.model_dump())
db.add(db_submission)
db.commit()
db.refresh(db_submission)
return {"message": "Action Submission created successfully", "id": db_submission.id}
22 changes: 20 additions & 2 deletions app/api/audit_events.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.core.logging_config import logger
from app.db.database import get_db
from app.db.models import AuditEvent
from app.utils.admin_check import admin_lock

router = APIRouter()


@router.get("/", tags=["audit_event"])
def list_rules():
def health_check_audit_events():
logger.info("Testing Audit_Event API call.")
return {"message": "Audit Event endpoint ready"}


@router.get("/get_audit_event/{audit_event_id}", tags=["audit_event"])
def retrieve_audit_event(audit_event_id: int, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
audit_event = db.query(AuditEvent).filter(AuditEvent.id == audit_event_id).first()
if not audit_event:
raise HTTPException(
status_code=404, detail=f"Audit Event with ID {audit_event_id} not found"
)
return audit_event
22 changes: 20 additions & 2 deletions app/api/decisions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.core.logging_config import logger
from app.db.models.decision import Decision
from app.db.database import get_db
from app.utils.admin_check import admin_lock

router = APIRouter()


@router.get("/", tags=["decisions"])
def list_rules():
def health_check_decisions():
logger.info("Testing Decisions API call.")
return {"message": "Decisions endpoint ready"}


@router.get("/get_decision/{decision_id}", tags=["decisions"])
def retrieve_request(decision_id: int, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
decision = db.query(Decision).filter(Decision.id == decision_id).first()
if not decision:
raise HTTPException(
status_code=404, detail=f"Decision with ID {decision_id} not found"
)
return decision
82 changes: 80 additions & 2 deletions app/api/policies.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,87 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from requests import Session

from app.core.logging_config import logger
from app.db.database import get_db
from app.db.models import Policy, Rule
from app.schemas.policy.create_policy import PolicyCreate
from app.utils.admin_check import admin_lock

router = APIRouter()


@router.get("/", tags=["policies"])
def list_rules():
def health_check_policies():
logger.info("Testing Policies API call.")
return {"message": "Policies endpoint ready"}


@router.get("/get_policy/{policy_id}", tags=["policies"])
def get_policy(policy_id: int, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
policy = db.query(Policy).filter(Policy.id == policy_id).first()
if not policy:
raise HTTPException(
status_code=404, detail=f"Policy with ID {policy_id} not found"
)
return policy


@router.post("/create_policy", tags=["policies"])
def create_policy(policy: PolicyCreate, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
db_submission = Policy(**policy.model_dump())
db.add(db_submission)
db.commit()
db.refresh(db_submission)
return {"message": "Policy created successfully", "id": db_submission.id}


@router.delete("/delete_policy/{policy_id}", tags=["policies"])
def delete_policy(policy_id, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
policy = db.query(Policy).filter(Policy.id == policy_id).first()

if not policy:
raise HTTPException(
status_code=404, detail=f"Policy with ID {policy_id} not found"
)

if db.query(Rule).filter(Rule.policy_id == policy_id).all():
raise HTTPException(
status_code=409,
detail="Please remove all rules from the policy before deleting the policy",
)

db.delete(policy)
db.commit()
return {"message": f"Policy with ID {policy_id} deleted successfully"}


@router.put("/edit_policy/{policy_id}", tags=["policies"])
def edit_policy(
policy_id: int, policy_update: PolicyCreate, db: Session = Depends(get_db)
):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()

existing_policy = db.query(Policy).filter(Policy.id == policy_id).first()
if not existing_policy:
raise HTTPException(
status_code=404, detail=f"Policy with id {policy_id} not found."
)

for key, value in policy_update.model_dump(exclude_unset=True).items():
setattr(existing_policy, key, value)

db.commit()
db.refresh(existing_policy)

return {"message": "Policy updated successfully", "id": existing_policy.id}
73 changes: 71 additions & 2 deletions app/api/rules.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,78 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from requests import Session
from app.core.logging_config import logger
from app.db.database import get_db
from app.db.models import Rule, Policy
from app.schemas.rule.create_rule import RuleCreate
from app.utils.admin_check import admin_lock

router = APIRouter()


@router.get("/", tags=["rules"])
def list_rules():
def health_check_rules():
logger.info("Testing Rules API call.")
return {"message": "Rules endpoint ready"}


@router.get("/get_rule/{rule_id}", tags=["rules"])
def get_rule(rule_id: int, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
rule = db.query(Rule).filter(Rule.id == rule_id).first()
if not rule:
raise HTTPException(status_code=404, detail=f"Rule with ID {rule_id} not found")
return rule


@router.post("/create_rule", tags=["rules"])
def create_rule(rule: RuleCreate, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()
db_submission = Rule(**rule.model_dump())

rule = db.query(Policy).filter(Policy.id == db_submission.policy_id).first()
if not rule:
raise HTTPException(
status_code=422,
detail="Policy ID " + str(db_submission.policy_id) + " is invalid",
)

db.add(db_submission)
db.commit()
db.refresh(db_submission)
return {"message": "Rule created successfully", "id": db_submission.id}


@router.delete("/delete_rule/{rule_id}", tags=["rules"])
def delete_rule(rule_id: int, db: Session = Depends(get_db)):
rule = db.query(Rule).filter(Rule.id == rule_id).first()

if not rule:
raise HTTPException(status_code=404, detail=f"Rule with ID {rule_id} not found")
db.delete(rule)
db.commit()
return {"message": f"Rule with ID {rule_id} deleted successfully"}


@router.put("/edit_rule/{rule_id}", tags=["Rules"])
def edit_rule(rule_id: int, rule_update: RuleCreate, db: Session = Depends(get_db)):
# TODO: Restrict this endpoint to admin only
# TODO: Add role-based access (RBAC)
admin_lock()

existing_rule = db.query(Rule).filter(Rule.id == rule_id).first()
if not existing_rule:
raise HTTPException(
status_code=404, detail=f"Rule with id {rule_id} not found."
)

for key, value in rule_update.model_dump(exclude_unset=True).items():
setattr(existing_rule, key, value)

db.commit()
db.refresh(existing_rule)

return {"message": "Rule updated successfully", "id": existing_rule.id}
16 changes: 2 additions & 14 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,19 @@


class Settings(BaseSettings):

app_env: str = "dev"
app_version: str = "1.0.0"
app_name: str = "RuleCheck"
log_level: str = "INFO"

debug: bool = False

db_user: str
db_password: str
db_name: str
db_host: str = "localhost"
db_port: int = 5432

@property
def database_url(self) -> str:

return (
f"postgresql://{self.db_user}:{self.db_password}"
f"@{self.db_host}:{self.db_port}/{self.db_name}"
)
database_url: str

class Config:

env_file = f".env"
env_file = ".env"
case_sensitive = False


Expand Down
Loading