Skip to content

Commit

Permalink
multi tanent user role
Browse files Browse the repository at this point in the history
  • Loading branch information
sanudutta45 committed Dec 23, 2024
1 parent 8110bc4 commit 5ec13d0
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 40 deletions.
4 changes: 2 additions & 2 deletions backend/api/UsersView.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def decorator(f):
async def decorated_function(*args, **kwargs):
cb = CasbinRoleManager()
token_info = context.context['token_info']
if not cb.check_permissions(token_info["role"], resourceId, action):
raise Forbidden(description="Insufficient permissions")
if not cb.check_permissions(token_info["uid"], action, resourceId):
raise Forbidden(detail="Insufficient permissions")

return await f(*args, **kwargs)

Expand Down
26 changes: 11 additions & 15 deletions backend/managers/AuthManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@
from webauthn.helpers.cose import COSEAlgorithmIdentifier
from connexion.exceptions import Unauthorized
from common.utils import get_env_key
import casbin
import os
from pathlib import Path
from common.mail import send
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
from jinja2 import Environment, FileSystemLoader

from backend.managers.CasbinRoleManager import CasbinRoleManager
# set up logging
from common.log import get_logger
logger = get_logger(__name__)
Expand Down Expand Up @@ -134,14 +133,6 @@ def __init__(self):
# policy_path = Path(__file__).parent.parent / 'rbac_policy.csv'
# self.enforcer = casbin.Enforcer(str(model_path), str(policy_path))

def check_permission(self, sub, obj, act):
return self.enforcer.enforce(sub, obj, act)

def add_role_for_user(self, user, role):
self.enforcer.add_grouping_policy(user, role)

def get_roles_for_user(self, user):
return self.enforcer.get_roles_for_user(user)

async def auth_options(self, email_id: str):
async with db_session_context() as session:
Expand Down Expand Up @@ -303,15 +294,17 @@ async def webauthn_login(self, challenge: str, email_id:str, response):
if not res.new_sign_count != 1:
return None

cb = CasbinRoleManager()
role = cb.get_user_role(user.id)
payload = {
"sub": user.id,
"role": user.role,
"role": role,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(days=1)
}
token = generate_jwt(payload)

return token, user.role
return token, role
async def verify_email(self, token: str):
async with db_session_context() as session:
user_id = verify_email_token(token)
Expand All @@ -326,10 +319,13 @@ async def verify_email(self, token: str):

user.emailVerified = True

admin_user_result = await session.execute(select(User).where(User.role == "admin"))
admin_user = admin_user_result.scalar_one_or_none()
cb = CasbinRoleManager()
admin_user = cb.get_admin_users()
if not admin_user:
user.role = "admin"
cb.assign_user_role(user.id)
else:
cb.assign_user_role(user.id, "user")

await session.commit()

return True
Expand Down
37 changes: 24 additions & 13 deletions backend/managers/CasbinRoleManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,48 @@ def init_casbin(self):

def add_default_rules(self):
default_rules = [
("user", "DEFAULT", "list"),
("user", "DEFAULT", "show"),
("admin", "DEFAULT", "create"),
("admin", "DEFAULT", "edit"),
("admin", "DEFAULT", "delete")
("user", "ADMIN_PORTAL", "ALL", "list"),
("user", "ADMIN_PORTAL", "ALL", "show"),
("admin", "ADMIN_PORTAL", "ALL", "list"),
("admin", "ADMIN_PORTAL", "ALL", "show"),
("admin", "ADMIN_PORTAL", "ALL", "create"),
("admin", "ADMIN_PORTAL", "ALL", "edit"),
("admin", "ADMIN_PORTAL", "ALL", "delete")
]

for rule in default_rules:
self.enforcer.add_policy(*rule)

self.enforcer.add_grouping_policy("admin", "user")

def get_enforcer(self):
return self.enforcer

def check_permissions(self, role, resource, resource_type):
return self.enforcer.enforce(role, resource, resource_type)
def check_permissions(self, user_id, res_act, res_id, domain="ADMIN_PORTAL"):
return self.enforcer.enforce(user_id, domain, res_id, res_act)

def get_permissions(self, role="user"):
return self.enforcer.get_implicit_permissions_for_user(role)
def get_permissions(self, role, domain="ADMIN_PORTAL"):
return self.enforcer.get_permissions_for_user_in_domain(role, domain)

def create_resource_access(self, role):
permissions = self.get_permissions(role)

output = {}
for _, resource, action in permissions:
for _,_, resource, action in permissions:
if resource not in output:
output[resource] = []

if action not in output[resource]:
output[resource].append(action)

return output

def assign_user_role(self, user_id, role="admin", domain="ADMIN_PORTAL"):
self.enforcer.add_role_for_user_in_domain(user_id, role, domain)

def get_admin_users(self, domain="ADMIN_PORTAL"):
return self.enforcer.get_users_for_role_in_domain("admin",domain)

def get_roles_for_user_in_domain(self, user_id, domain="ADMIN_PORTAL"):
return self.enforcer.get_roles_for_user_in_domain(user_id, domain)

def get_user_role(self, user_id, domain="ADMIN_PORTAL"):
return ",".join(self.enforcer.get_roles_for_user_in_domain(user_id, domain))

9 changes: 6 additions & 3 deletions backend/managers/UsersManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from backend.models import User
from backend.db import db_session_context
from backend.schemas import UserSchema
from backend.managers.CasbinRoleManager import CasbinRoleManager

class UsersManager:
_instance = None
Expand Down Expand Up @@ -46,7 +47,8 @@ async def retrieve_user(self, id):
async with db_session_context() as session:
result = await session.execute(select(User).filter(User.id == id))
user = result.scalar_one_or_none()
return UserSchema(id=user.id, name=user.name, email=user.email, role=user.role) if user else None
cb = CasbinRoleManager()
return UserSchema(id=user.id, name=user.name, email=user.email, role=cb.get_user_role(user.id)) if user else None

async def retrieve_users(self, offset=0, limit=100, sort_by=None, sort_order='asc', filters=None):
async with db_session_context() as session:
Expand All @@ -59,18 +61,19 @@ async def retrieve_users(self, offset=0, limit=100, sort_by=None, sort_order='as
else:
query = query.filter(getattr(User, key) == value)

if sort_by and sort_by in ['id', 'name', 'email', 'role']:
if sort_by and sort_by in ['id', 'name', 'email']:
order_column = getattr(User, sort_by)
query = query.order_by(order_column.desc() if sort_order.lower() == 'desc' else order_column)

query = query.offset(offset).limit(limit)

result = await session.execute(query)
cb = CasbinRoleManager()
users = [UserSchema(
id=user.id,
name=user.name,
email=user.email,
role=user.role
role=cb.get_user_role(user.id)
) for user in result.scalars().all()]

# Get total count
Expand Down
1 change: 0 additions & 1 deletion backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class User(SQLModelBase, table=True):
creds: List["Cred"] = Relationship(back_populates="user")
sessions: List["Session"] = Relationship(back_populates="user")
emailVerified: bool = Field(default=False)
role: str = Field(default="user")

class Cred(SQLModelBase, table=True):
id: str = Field(primary_key=True, default_factory=lambda: str(uuid4()))
Expand Down
9 changes: 4 additions & 5 deletions backend/rbac_model.conf
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
[request_definition]
r = role, res_id, act
r = sub, dom, obj, act

[policy_definition]
p = role, res_id, act
p = sub, dom, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = ((r.res_id == p.res_id || p.res_id == "DEFAULT") && r.act == p.act && g(r.role,p.role))

m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && (r.obj == p.obj || p.obj == "ALL") && r.act == p.act
[role_definition]
g = _, _
g = _, _, _
2 changes: 1 addition & 1 deletion frontend/src/utils/authUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const hasAccess = (
action: string,
permissions: ResourcePermissions
) => {
const resource = permissions[resourceId] || permissions["DEFAULT"];
const resource = permissions[resourceId] || permissions["ALL"];
if (!resource) return false;
const hasAccess = resource.includes(action);
return hasAccess;
Expand Down
31 changes: 31 additions & 0 deletions migrations/versions/e7cfcff87b8e_remove_role_from_user_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""remove role from user table
Revision ID: e7cfcff87b8e
Revises: 008645bff529
Create Date: 2024-12-23 18:37:01.454972
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import sqlmodel


# revision identifiers, used by Alembic.
revision: str = 'e7cfcff87b8e'
down_revision: Union[str, None] = '008645bff529'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user', 'role')
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('role', sa.VARCHAR(), nullable=False))
# ### end Alembic commands ###

0 comments on commit 5ec13d0

Please sign in to comment.