diff --git a/.alembic/versions/2026-06-16_org_perm_perms_join_table.py b/.alembic/versions/2026-06-16_org_perm_perms_join_table.py deleted file mode 100644 index c0043c5..0000000 --- a/.alembic/versions/2026-06-16_org_perm_perms_join_table.py +++ /dev/null @@ -1,38 +0,0 @@ -"""org perm perms join table - -Revision ID: 85edbf9a176c -Revises: 98e20aae555c -Create Date: 2026-06-16 13:31:57.427953 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '85edbf9a176c' -down_revision: Union[str, Sequence[str], None] = '98e20aae555c' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - """Upgrade schema.""" - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('org_permissions', - sa.Column('org_id', sa.Integer(), nullable=False), - sa.Column('permission_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['org_id'], ['organisation.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['permission_id'], ['permission.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('org_id', 'permission_id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - """Downgrade schema.""" - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('org_permissions') - # ### end Alembic commands ### diff --git a/src/iam/models.py b/src/iam/models.py index a06ff79..5e9b821 100644 --- a/src/iam/models.py +++ b/src/iam/models.py @@ -42,7 +42,7 @@ class Permission(Base): ), ) - service_rel = relationship("Service", foreign_keys="Permission.service_id") + service_rel = relationship("Service", foreign_keys=[service_id]) @property def service_name(self): @@ -52,10 +52,6 @@ class Permission(Base): "Group", secondary="group_permissions", back_populates="permission_rel" ) - org_rel = relationship( - "Organisation", secondary="org_permissions", back_populates="permission_rel" - ) - class Group(Base): __tablename__ = "group" @@ -99,13 +95,3 @@ class UserGroups(Base): group_id = Column( Integer, ForeignKey("group.id", ondelete="CASCADE"), primary_key=True ) - - -class OrgPermissions(Base): - __tablename__ = "org_permissions" - org_id = Column( - Integer, ForeignKey("organisation.id", ondelete="CASCADE"), primary_key=True - ) - permission_id = Column( - Integer, ForeignKey("permission.id", ondelete="CASCADE"), primary_key=True - ) diff --git a/src/iam/router.py b/src/iam/router.py index 69073ea..10dc52e 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -20,7 +20,6 @@ Endpoints: from fastapi import APIRouter, status, BackgroundTasks from sqlalchemy.exc import IntegrityError -from psycopg.errors import UniqueViolation from src.iam.exceptions import GroupNotFoundException from src.organisation.exceptions import OrgNotFoundException @@ -284,11 +283,10 @@ async def create_group( db.flush() except IntegrityError as e: if ( - isinstance(e.orig, UniqueViolation) # Postgres unique violation + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation ): raise ConflictException("Group with this name already exists") - raise group_response = GroupSummary(**group_model.__dict__) org_response = OrgSummary(**org_model.__dict__) db.commit() @@ -325,9 +323,6 @@ async def add_group_permission( if perm_model in group_model.permission_rel: raise ConflictException("Group already has this permission") - if perm_model not in org_model.permission_rel: # TODO: and not su - raise ForbiddenException("You cannot grant this permission") - group_model.permission_rel.append(perm_model) db.flush() @@ -474,10 +469,8 @@ async def get_permissions( """ Returns a full list of permissions. """ - # TODO: if su: - # permission_models = db.query(Perm).all() - # else - permission_models = db.query(Perm).filter(Perm.org_rel.any(id=org_model.id)).all() + permission_models = db.query(Perm).all() + return {"permissions": permission_models} @@ -507,11 +500,10 @@ async def create_new_permission( db.flush() except IntegrityError as e: if ( - isinstance(e.orig, UniqueViolation) # Postgres unique violation + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation ): raise ConflictException(message="Permission already exists") - raise response = { "id": perm_model.id, "service_name": perm_model.service_name, @@ -571,9 +563,6 @@ async def post_permissions( if not (request_model.action is None or request_model.action == ""): permission_query = permission_query.filter(Perm.action == request_model.action) - # TODO: if not su: - permission_query = permission_query.filter(Perm.org_rel.any(id=org_model.id)) - permission_models = permission_query.all() return {"permissions": permission_models} diff --git a/src/organisation/models.py b/src/organisation/models.py index c333c40..e99d64f 100644 --- a/src/organisation/models.py +++ b/src/organisation/models.py @@ -39,25 +39,15 @@ class Organisation(Base): ) group_rel = relationship("Group", back_populates="org_rel") - root_user_rel = relationship("User", foreign_keys="Organisation.root_user_id") + root_user_rel = relationship("User", foreign_keys=[root_user_id]) @property def root_user_email(self): return self.root_user_rel.email if self.root_user_rel else None - billing_contact_rel = relationship( - "Contact", foreign_keys="Organisation.billing_contact_id" - ) - security_contact_rel = relationship( - "Contact", foreign_keys="Organisation.security_contact_id" - ) - owner_contact_rel = relationship( - "Contact", foreign_keys="Organisation.owner_contact_id" - ) - - permission_rel = relationship( - "Permission", secondary="org_permissions", back_populates="org_rel" - ) + billing_contact_rel = relationship("Contact", foreign_keys=[billing_contact_id]) + security_contact_rel = relationship("Contact", foreign_keys=[security_contact_id]) + owner_contact_rel = relationship("Contact", foreign_keys=[owner_contact_id]) class OrgUsers(Base): diff --git a/src/organisation/router.py b/src/organisation/router.py index a7114c3..f0f212a 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -18,11 +18,10 @@ Endpoints: from datetime import datetime, timezone from typing import Annotated -from sqlalchemy.exc import IntegrityError -from psycopg.errors import UniqueViolation from fastapi import APIRouter, status from fastapi.params import Query +from sqlalchemy.exc import IntegrityError from src.contact.schemas import ContactModel from src.exceptions import ( @@ -176,13 +175,12 @@ async def create_org( db.flush() except IntegrityError as e: if ( - isinstance(e.orig, UniqueViolation) # Postgres unique violation + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation ): raise ConflictException( message="Organisation with this name already exists" ) - raise # Adds currently logged-in user to org users list and sets them as root_user org_model.user_rel.append(user_model) org_model.root_user_rel = user_model diff --git a/src/service/router.py b/src/service/router.py index d4954fd..434d9bc 100644 --- a/src/service/router.py +++ b/src/service/router.py @@ -10,7 +10,6 @@ Endpoints: from fastapi import APIRouter, status from sqlalchemy.exc import IntegrityError -from psycopg.errors import UniqueViolation from src.exceptions import ConflictException from src.database import db_dependency @@ -111,11 +110,10 @@ async def register_service( db.flush() except IntegrityError as e: if ( - isinstance(e.orig, UniqueViolation) # Postgres unique violation + getattr(e.orig, "pgcode", None) == "23505" # Postgres unique violation or "UNIQUE constraint failed" in str(e.orig) # SQLite unique violation ): raise ConflictException(message="Service with this name already exists") - raise response = ServiceWithKeySchema(**service_model.__dict__) db.commit() return {"service": response} diff --git a/test/conftest.py b/test/conftest.py index 417cd68..a36e9a3 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -10,7 +10,7 @@ from src.user.models import User from src.service.models import Service from src.organisation.models import Organisation as Org, OrgUsers from src.contact.models import Contact -from src.iam.models import Group, Permission, OrgPermissions +from src.iam.models import Group, Permission from src.auth.service import get_current_user, get_dev_user from src.auth.dependencies import empty_su_list, get_super_admin_list, testing_su_list from src.main import app # inited FastAPI app @@ -163,9 +163,6 @@ def _seed(db): db.add(Service(name="Test Service", api_key="123456789")) db.add(Permission(service_id=1, resource="test_resource", action="read")) db.add(Permission(service_id=1, resource="test_resource", action="move")) - db.add(Permission(service_id=1, resource="test_resource", action="delete")) - db.add(OrgPermissions(org_id=1, permission_id=1)) - db.add(OrgPermissions(org_id=1, permission_id=2)) db.add(Group(name="Org One Group", org_id=1)) db.add(Group(name="Org Two Group", org_id=2)) db.add(Group(name="Org One Group Two", org_id=1)) diff --git a/test/test_iam.py b/test/test_iam.py index e65b630..b9e597c 100644 --- a/test/test_iam.py +++ b/test/test_iam.py @@ -437,7 +437,7 @@ async def test_post_perm_success(default_client: AsyncClient): assert "permission" in data assert isinstance(data["permission"], dict) - assert data["permission"]["id"] == 4 + assert data["permission"]["id"] == 3 assert data["permission"]["service_name"] == "Test Service" assert data["permission"]["resource"] == "test_resource" assert data["permission"]["action"] == "create"