Skip to content

Commit eb58d4d

Browse files
Default permissions will be global, but default roles will be organization-specific
1 parent ad71d96 commit eb58d4d

File tree

6 files changed

+68
-25
lines changed

6 files changed

+68
-25
lines changed

main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
@asynccontextmanager
2121
async def lifespan(app: FastAPI):
2222
# Optional startup logic
23-
set_up_db(drop=False)
23+
# TODO: Set drop=False in production
24+
set_up_db(drop=True)
2425
yield
2526
# Optional shutdown logic
2627

routers/organization.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from sqlmodel import Session, select
66
from utils.db import get_session
77
from utils.auth import get_authenticated_user
8-
from utils.models import Organization, User, Role, UserOrganizationLink, ValidPermissions, utc_time
8+
from utils.models import Organization, User, Role, Permission, UserOrganizationLink, ValidPermissions, utc_time
99
from datetime import datetime
1010
from sqlalchemy import and_
1111
from utils.role_org import get_organization, check_user_permission
@@ -119,6 +119,15 @@ def create_organization(
119119
session.commit()
120120
session.refresh(db_org)
121121

122+
# Create default roles
123+
default_role_names = ["Owner", "Administrator", "Member"]
124+
default_roles = []
125+
for role_name in default_role_names:
126+
role = Role(name=role_name, organization_id=db_org.id)
127+
session.add(role)
128+
default_roles.append(role)
129+
session.commit()
130+
122131
owner_role = session.exec(
123132
select(Role).where(
124133
and_(

tests/test_models.py

Whitespace-only changes.

utils/db.py

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,51 @@ def get_session():
3939
yield session
4040

4141

42-
def create_roles(session):
42+
def create_default_roles(session, organization_id: int, check_first: bool = True):
4343
"""
44-
Create default roles in the database if they do not exist.
44+
Create default roles for an organization in the database if they do not exist.
4545
"""
4646
roles_in_db = []
4747
for role_name in default_roles:
4848
db_role = session.exec(select(Role).where(
49-
Role.name == role_name)).first()
49+
Role.name == role_name,
50+
Role.organization_id == organization_id
51+
)).first()
5052
if not db_role:
51-
db_role = Role(name=role_name)
53+
db_role = Role(name=role_name, organization_id=organization_id)
5254
session.add(db_role)
5355
roles_in_db.append(db_role)
56+
57+
# Create RolePermissionLink for Owner and Administrator roles
58+
for role in roles_in_db[:2]:
59+
permissions = session.exec(select(Permission)).all()
60+
for permission in permissions:
61+
# Check if the role already has the permission
62+
if check_first:
63+
db_role_permission_link: RolePermissionLink | None = session.exec(select(RolePermissionLink).where(
64+
RolePermissionLink.role_id == role.id,
65+
RolePermissionLink.permission_id == permission.id
66+
)).first()
67+
else:
68+
db_role_permission_link = None
69+
70+
# Skip giving DELETE_ORGANIZATION permission to Administrator
71+
if not db_role_permission_link and not (
72+
permission == ValidPermissions.DELETE_ORGANIZATION and
73+
role.name == "Administrator"
74+
):
75+
role_permission_link = RolePermissionLink(
76+
role_id=role.id,
77+
permission_id=permission.id
78+
)
79+
session.add(role_permission_link)
80+
5481
return roles_in_db
5582

5683

57-
def create_permissions(session, roles_in_db):
84+
def create_permissions(session):
5885
"""
59-
Create default permissions and link them to roles in the database.
86+
Create default permissions.
6087
"""
6188
for permission in ValidPermissions:
6289
db_permission = session.exec(select(Permission).where(
@@ -65,17 +92,6 @@ def create_permissions(session, roles_in_db):
6592
db_permission = Permission(name=permission)
6693
session.add(db_permission)
6794

68-
# Create RolePermissionLink for Owner and Administrator
69-
for role in roles_in_db[:2]:
70-
db_role_permission_link = session.exec(select(RolePermissionLink).where(
71-
RolePermissionLink.role_id == role.id,
72-
RolePermissionLink.permission_id == db_permission.id)).first()
73-
if not db_role_permission_link:
74-
if not (permission == ValidPermissions.DELETE_ORGANIZATION and role.name == "Administrator"):
75-
role_permission_link = RolePermissionLink(
76-
role_id=role.id, permission_id=db_permission.id)
77-
session.add(role_permission_link)
78-
7995

8096
def set_up_db(drop: bool = False):
8197
"""
@@ -85,10 +101,9 @@ def set_up_db(drop: bool = False):
85101
if drop:
86102
SQLModel.metadata.drop_all(engine)
87103
SQLModel.metadata.create_all(engine)
104+
# Create default permissions
88105
with Session(engine) as session:
89-
roles_in_db = create_roles(session)
90-
session.commit()
91-
create_permissions(session, roles_in_db)
106+
create_permissions(session)
92107
session.commit()
93108
engine.dispose()
94109

utils/models.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,23 @@ class Organization(SQLModel, table=True):
6262

6363

6464
class Role(SQLModel, table=True):
65+
"""
66+
Represents a role within an organization.
67+
68+
Attributes:
69+
id: Primary key.
70+
name: The name of the role.
71+
organization_id: Foreign key to the associated organization.
72+
created_at: Timestamp when the role was created.
73+
updated_at: Timestamp when the role was last updated.
74+
"""
6575
id: Optional[int] = Field(default=None, primary_key=True)
6676
name: str
6777
organization_id: int = Field(foreign_key="organization.id")
6878
created_at: datetime = Field(default_factory=utc_time)
6979
updated_at: datetime = Field(default_factory=utc_time)
7080

71-
organization: Organization = Relationship(back_populates="roles")
81+
organization: "Organization" = Relationship(back_populates="roles")
7282
user_links: List[UserOrganizationLink] = Relationship(
7383
back_populates="role")
7484
permissions: List["Permission"] = Relationship(
@@ -78,6 +88,9 @@ class Role(SQLModel, table=True):
7888

7989

8090
class Permission(SQLModel, table=True):
91+
"""
92+
Represents a permission that can be assigned to a role.
93+
"""
8194
id: Optional[int] = Field(default=None, primary_key=True)
8295
name: ValidPermissions = Field(
8396
sa_column=Column(SQLAlchemyEnum(ValidPermissions, create_type=False)))

utils/role_org.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List
22
from sqlmodel import Session, select
3-
from sqlalchemy import and_
3+
from sqlalchemy import and_, or_
44
from fastapi import HTTPException
55
from utils.models import Organization, Role, UserOrganizationLink, RolePermissionLink, Permission, ValidPermissions
66

@@ -150,6 +150,11 @@ def get_organization_roles(
150150
Returns:
151151
List of Role objects with their associated permissions
152152
"""
153-
query = select(Role).where(Role.organization_id == organization_id)
153+
query = select(Role).where(
154+
or_(
155+
Role.organization_id == organization_id,
156+
Role.organization_id == None
157+
)
158+
)
154159

155160
return list(session.exec(query))

0 commit comments

Comments
 (0)