diff --git a/compose.yaml b/compose.yaml index 6d78124..01ef88f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -47,12 +47,18 @@ services: environment: DB_HOST: db DB_PORT: 5432 - command: ["python","/src/__main__.py"] + DB_NAME: library + POSTGRES_PASSWORD: ${DB_PASS} + CASBIN_LOGIN_PASS: ${CASBIN_LOGIN_PASS} + command: sh -c "python /src/auth/policySetup.py && python /src/__main__.py" depends_on: db: condition: service_healthy ports: - "8000:8000" + secrets: + - casbin_login_pass + - db_pass db: # Database # entrypoint: ['docker-entrypoint.sh'] image: postgres:17.5-alpine3.21 # Using alpine Postgres image @@ -62,10 +68,11 @@ services: restart: unless-stopped volumes: - pgdata:/var/lib/postgresql/data - - db-init:/docker-entrypoint-initdb.d:ro # Init scripts for postgres + - ./db-init:/docker-entrypoint-initdb.d:ro environment: POSTGRES_USER: library POSTGRES_PASSWORD: ${DB_PASS} + CASBIN_LOGIN_PASS: ${CASBIN_LOGIN_PASS} POSTGRES_DB: library healthcheck: @@ -104,7 +111,6 @@ networks: # Frontend and DB MUST NEVER be on the same network. volumes: pgdata: - db-init: secrets: casbin_login_pass: diff --git a/db-init/0_roles.sh b/db-init/0_roles.sh new file mode 100755 index 0000000..5325b42 --- /dev/null +++ b/db-init/0_roles.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE ROLE casbin NOLOGIN; + CREATE ROLE casbin_login LOGIN PASSWORD '${CASBIN_LOGIN_PASS}'; + GRANT casbin TO casbin_login; + + GRANT CREATE, USAGE ON SCHEMA public TO casbin_login; + GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO casbin_login; +EOSQL \ No newline at end of file diff --git a/db-init/0_roles.sql b/db-init/0_roles.sql deleted file mode 100644 index ebaacde..0000000 --- a/db-init/0_roles.sql +++ /dev/null @@ -1,29 +0,0 @@ -/* -# This program is for the automatic creation of database roles upon first start. -# Copyright (C) 2025, The CS Nerds (HippoProgrammer & SuitablyMysterious) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see .*/ - - --- Create casbin role -CREATE ROLE casbin NOLOGIN; -CREATE ROLE casbin_login LOGIN PASSWORD :casbin_login_pass; -GRANT casbin TO casbin_login; - --- Create server admin, write and read roles -CREATE ROLE server_admin LOGIN PASSWORD 'SUBSTITUTE_PASSWORD'; -CREATE ROLE server_write LOGIN PASSWORD 'SUBSTITUTE_PASSWORD'; -CREATE ROLE server_read LOGIN PASSWORD 'SUBSTITUTE_PASSWORD'; - -CREATE ROLE zitadel LOGIN PASSWORD 'zitadel'; \ No newline at end of file diff --git a/db-init/1_tables.sql b/db-init/1_tables.sql index 1f239c7..796aff0 100644 --- a/db-init/1_tables.sql +++ b/db-init/1_tables.sql @@ -39,5 +39,9 @@ CREATE TABLE IF NOT EXISTS users ( surname TEXT NOT NULL, student_id INTEGER NOT NULL email TEXT NOT NULL UNIQUE, - role TEXT NOT NULL, -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS casbin_users ( + id UUID PRIMARY KEY, + role TEXT NOT NULL +) \ No newline at end of file diff --git a/launch.sh b/launch.sh index 8d16b5d..eb46e3d 100755 --- a/launch.sh +++ b/launch.sh @@ -1,12 +1,15 @@ #!/bin/sh -export DB_PASS=`python3 dbKeyGen.py --keys db --print` -export CASBIN_LOGIN_PASS=`python3 dbKeyGen.py --keys auth --print` +docker compose down -v +export DB_PASS=`python dbKeyGen.py --keys db --print` +export CASBIN_LOGIN_PASS=`python dbKeyGen.py --keys auth --print` if [ $1 = "--kill" ]; then - docker compose down + docker compose down -v docker system prune --force + docker volume prune --force exit 0 else - docker compose up --build $1 + + docker compose up --build $1 backend db frontend fi unset DB_PASS unset CASBIN_LOGIN_PASS \ No newline at end of file diff --git a/src/backend/__main__.py b/src/backend/__main__.py index 7093dad..7f85691 100644 --- a/src/backend/__main__.py +++ b/src/backend/__main__.py @@ -50,9 +50,9 @@ class getBookByID(Resource): def get(self, id): return db.getBookData(id = int(id)) -api.add_resource(getBookByISBN, '/Book/getByISBN') -api.add_resource(getBookByName, '/Book/getByName') -api.add_resource(getBookByID, '/Book/getByID') +api.add_resource(getBookByISBN, '/Book/getByISBN/') +api.add_resource(getBookByName, '/Book/getByName/') +api.add_resource(getBookByID, '/Book/getByID/') if __name__ == '__main__': app.run() \ No newline at end of file diff --git a/src/backend/auth/model.conf b/src/backend/auth/model.conf index 4854b51..ce3c477 100644 --- a/src/backend/auth/model.conf +++ b/src/backend/auth/model.conf @@ -22,7 +22,7 @@ r = sub, tbl, obj, act, verified # needs to be changed later on p = sub, tbl, obj, act, verified # needs to be changed later on [role_definition] -g = _, _, _, _, _, _ # unique id, student id, name, email, role, password +g = _, _ # uuid, permissions [policy_effect] e = some(where (p.eft == allow)) diff --git a/src/backend/auth/policy.csv b/src/backend/auth/policy.csv index 5cf12e8..99d2804 100644 --- a/src/backend/auth/policy.csv +++ b/src/backend/auth/policy.csv @@ -5,4 +5,5 @@ p,librarian,loans,loan,delete,True p,student,books,book,read,True p,*,books,book,read,False p,admin,*,*,*,True -g,000000,0000,admin,admin,root \ No newline at end of file +p,code,*,*,*,True +g,0,code \ No newline at end of file diff --git a/src/backend/auth/policySetup.py b/src/backend/auth/policySetup.py index b57e4c4..840e117 100644 --- a/src/backend/auth/policySetup.py +++ b/src/backend/auth/policySetup.py @@ -18,7 +18,7 @@ import logging import sys import os -from casbin import Enforcer, persist +from casbin import Enforcer from casbin_sqlalchemy_adapter import Adapter from sqlalchemy import create_engine @@ -26,7 +26,7 @@ log_handler = logging.StreamHandler(sys.stdout) log.addHandler(log_handler) -log.setLevel(logging.INFO) +log.setLevel(logging.DEBUG) log.info('Initiating policySetup.py') @@ -38,9 +38,9 @@ engine = create_engine(DSN, future=True) adapter = Adapter(engine) -enforcer = Enforcer(str(MODEL_PATH), adapter, auto_save=False) +enforcer = Enforcer(str(MODEL_PATH), adapter) enforcer.load_policy() -db_enforcer = Enforcer(str(MODEL_PATH), adapter, auto_save=False) +db_enforcer = Enforcer(str(MODEL_PATH), adapter) file_enforcer = Enforcer(str(MODEL_PATH), str(POLICY_PATH)) for rule in file_enforcer.get_policy(): diff --git a/src/backend/backend.Dockerfile b/src/backend/backend.Dockerfile index 2bb5001..eb7644a 100644 --- a/src/backend/backend.Dockerfile +++ b/src/backend/backend.Dockerfile @@ -24,7 +24,7 @@ RUN pip install --no-cache-dir --require-hashes --force-reinstall -r requirement RUN adduser --disabled-password --gecos '' appuser USER appuser -ADD ./* ./ +ADD . ./ EXPOSE 8000 diff --git a/src/backend/db.py b/src/backend/db.py index 935be94..4d760cc 100644 --- a/src/backend/db.py +++ b/src/backend/db.py @@ -20,6 +20,8 @@ from uuid import uuid4 from email_validator import validate_email, EmailNotValidError from casbin import Enforcer +from casbin_sqlalchemy_adapter import Adapter +from sqlalchemy import create_engine log = logging.getLogger(__name__) @@ -27,10 +29,16 @@ log.info('Read DB password') +MODEL_PATH = "auth/model.conf" +DB_URL = f"postgresql+psycopg://casbin_login:{os.environ['CASBIN_LOGIN_PASS']}@db:5432/library" +engine = create_engine(DB_URL) +adapter = Adapter(engine) +enforcer = Enforcer(MODEL_PATH, adapter) + class APIException(Exception): pass -def sendSQLCommand(command, userID, table, verified = True, fetch = 1): # NO USER INPUT SHOULD BE SENT DIRECTLY HERE +def sendSQLCommand(command, UUID, table, verified = True, fetch = 1): # NO USER INPUT SHOULD BE SENT DIRECTLY HERE verb = command.strip().split()[0].upper() action_map = { "SELECT": "read", @@ -39,7 +47,7 @@ def sendSQLCommand(command, userID, table, verified = True, fetch = 1): # NO USE "DELETE": "delete", #@HippoProgrammer Please update this as I know not much SQL } action = action_map.get(verb) - if Enforcer.enforce(userID, table, "*", action, verified): + if Enforcer.enforce(UUID, table, "*", action, verified): log.debug("User is authorized to perform this action") log.info('Connecting to postgres DB...') with psycopg.connect(f"postgres://library:{str(os.environ['DB_PASS'])}@db:5432/library") as conn: # create a connection to the db @@ -100,9 +108,9 @@ def __init__(self, forename: str, surname: str, student_id: int, email: str, rol def SQLStore(self): try: sendSQLCommand( - command="INSERT INTO users (id, forename, surname, student_id, email, role) VALUES (%s, %s, %s, %s, %s, %s)", - params=(self.uuid, self.forename, self.surname, self.student_id, self.email, self.role), - userID='admin', # Needs to updated later on + command="INSERT INTO users (id, forename, surname, student_id, email) VALUES (%s, %s, %s, %s, %s)", + params=(self.uuid, self.forename, self.surname, self.student_id, self.email), + UUID=0, # Ummm, is this correct @HippoProgrammer table='users', verified=True, fetch=0 @@ -111,9 +119,7 @@ def SQLStore(self): log.error(f"Failed to store user {self.student_id} in database: {e}") def addToCasbin(self): try: - enforcer = Enforcer("model.conf", "policy.csv") - enforcer.add_policy("user", self.uuid, "read", "book") - enforcer.add_grouping_policy(self.uuid, "group", self.role) + enforcer.add_grouping_policy(self.uuid, self.role) enforcer.save_policy() except Exception as e: log.error(f"Failed to add user {self.uuid} to Casbin: {e}")