9.6 KiB
Requirements
- team has its own dashboard tab in the UI
- COIs for a team are only visible to that team members and super admins.
- team can have multiple checklists
- separate email agents
- user can belong to multiple teams
RBAC
Key Concepts:
- Permission - Individual action (e.g.,
contract:create,checklist:edit) - Role - Collection of permissions (ROOT, ADMIN, VIEWER)
- TeamMembership - Links user to team with a role
-
users belong to an Organization
-
users can be granted access to multiple Teams
-
each team access has a specific Role
-
roles contain Permissions
-
all permissions are stored in
Permissiontable -
roles stored in
Roletable -
roles has many-to-many relationship with
Permissiontable -
link users to teams with specific roles
-
users can have different roles in different teams
-
permissions are derived from the assigned role
** Entity Relationship Schema**
Database Schema
class Organization():
name = models.CharField(max_length=200)
email = models.EmailField()
class User():
id = models.UUIDField()
name = models.CharField(max_length=100)
email = models.EmailField(max_length=100)
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
db_column="organization_id",
)
is_superuser = models.BooleanField(default=False) # root admin
Permissions: permissions
class Permission():
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
code = models.CharField(max_length=100, unique=True) # e.g., "contract:create"
name = models.CharField(max_length=200) # e.g., "Create Contract"
category = models.CharField(max_length=50) # e.g., "contract", "team", "checklist"
# description = models.TextField(blank=True)
Role: Collection of permissions
class Role():
name = models.CharField(max_length=50) # e.g., "ROOT", "ADMIN", "VIEWER"
description = models.TextField(blank=True)
is_system = models.BooleanField(default=False) # Flag for default/system roles
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="roles"
)
permissions = models.ManyToManyField(
Permission,
related_name="roles",
blank=True
)
- each organization has its own set of roles (along with default 3
systemroles i.e. "ROOT", "ADMIN", "VIEWER") system_rolescreated automatically with standard permissions, but can be customized per organization
Team: workspace within an organization
class Team():
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="teams"
)
settings = models.JSONField(default=dict)
TeamMembership: user role assignment in a team
class TeamMembership():
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="team_memberships"
)
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="memberships"
)
role = models.ForeignKey(
Role,
on_delete=models.PROTECT, # Prevent deletion of role if in use
related_name="team_memberships"
)
added_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
related_name="memberships_added"
)
class TeamEmailAgent():
team = models.OneToOneField(
Team,
on_delete=models.CASCADE,
related_name="email_agent"
)
provider_type = models.CharField(
max_length=20,
choices=ProviderType.choices,
default=ProviderType.MICROSOFT
)
inbox_email = models.EmailField(unique=True)
ms_client_id = models.CharField(max_length=500, blank=True)
ms_client_secret = models.CharField(max_length=500, blank=True) # maybe encrypt this
ms_tenant_id = models.CharField(max_length=500, blank=True)
subscription_id = models.CharField(max_length=500, null=True, blank=True)
subscription_expiry = models.DateTimeField(null=True, blank=True)
# processing settings
# {
# "default_checklist_id": "uuid",
# "automated_reminder": {} .....
# }
processing_settings = models.JSONField(default=dict)
enabled = models.BooleanField(default=False)
def get_access_token(self) -> str:
"""Get OAuth access token for email operations"""
from assistantService.services.graph_service.service import GraphEmailService
graph = GraphEmailService()
return graph.generate_access_token(
client_id=self.ms_client_id,
client_secret=self.ms_client_secret,
tenant_id=self.ms_tenant_id
)
Permission Naming Convention: resource:action
contract:view,contract:create,contract:edit,contract:delete,contract:analyzeteam:view,team:create,team:edit,team:delete,team:manage_memberschecklist:view,checklist:create,checklist:edit,checklist:deleteemail_agent:view,email_agent:configure,email_agent:enable,email_agent:disable
permission verifications
def is_superuser(user: User) -> bool:
return getattr(user, 'is_superuser', False)
def get_user_permissions(
user: User,
team_id: Optional[str] = None,
) -> Set[str]:
"""
get all permissions for a user in a team.
"""
cache_key = f"user_perms:{user.id}:{team_id}"
cached_perms = cache.get(cache_key)
if cached_perms is not None:
return set(cached_perms)
membership = TeamMembership.objects.filter(
user=user,
team_id=team_id,
).select_related('role').prefetch_related('role__permissions').first()
if not membership:
return set()
role_permissions = { p.code for p in membership.role.permissions.all() }
if use_cache:
cache.set(cache_key, list(role_permissions))
return role_permissions
def has_permission(
user: User,
permission_code: str,
team_id: Optional[str] = None,
) -> bool:
"""
check if user has a specific permission in a team.
"""
if is_superuser(user):
return True
if not team_id:
raise ValueError("team_id is required for permission check")
permissions = get_user_permissions(user, team_id, use_cache)
if permission_code in permissions:
return True
# NOTE: optional if we are allowing wildcard permissions
# resource, action = permission_code.split(":", 1) if ":" in permission_code else (permission_code, "")
# wildcard_permission = f"{resource}:*"
# if wildcard_permission in permissions:
# return True
return False
Usage
@api_view(["GET"])
@require_permission("contract:view", "team_id")
def list_contracts(request, team_id):
contracts = Contract.objects.filter(team_id=team_id, deleted_at__isnull=True)
return Response({"contracts": list(contracts.values())})
@api_view(["POST"])
@require_permission("contract:create", "team_id")
def upload_contract(request, team_id):
return Response({"message": "Contract uploaded"})
@api_view(["POST"])
@require_superuser()
def create_organization(request):
# Only superusers can create organizations
...
API Endpoints
Dashboard
-
GET /api/teams/{team_id}/dashboard- Get team dashboard with contracts and statistics- Required Permissions: Team access +
contract:view - Query Parameters:
page(optional),page_size(optional),filters(optional)
- Required Permissions: Team access +
-
GET /api/teams/{team_id}/dashboard/stats- Get team dashboard statistics- Required Permissions: Team access +
contract:view
- Required Permissions: Team access +
Checklists
GET /api/teams/{team_id}/checklists- List all checklists for a team- Required Permissions: Team access +
checklist:view
- Required Permissions: Team access +
team management within org
-
POST /api/teams- Create a new team in organization- Required Permission:
superuser - Request Body:
{ "organization_id": "uuid", "name": "string", "description": "string", "settings": {} }
- Required Permission:
-
PUT /api/teams/{team_id}- Update team settings- Required Permissions: Team access +
team:edit - Request Body:
{ "name": "string", "description": "string", "settings": {} }
- Required Permissions: Team access +
-
DELETE /api/teams/{team_id}- Delete a team (soft delete)- Required Permissions: Team access +
team:delete
- Required Permissions: Team access +
-
GET /api/teams/{team_id}/members- List all team members with their roles- Required Permissions: Team access +
team:view
- Required Permissions: Team access +
-
POST /api/teams/{team_id}/members- Add a user to a team with a specific role- Required Permissions: Team access +
team:manage_members - Request Body:
{ "user_id": "uuid", "role_name": "ROOT" | "ADMIN" | "VIEWER" }
- Required Permissions: Team access +
-
DELETE /api/teams/{team_id}/members/{user_id}- Remove a user from a team- Required Permissions: Team access +
team:manage_members
- Required Permissions: Team access +
-
PUT /api/teams/{team_id}/members/{user_id}/role- Update a team member's role- Required Permissions: Team access +
team:manage_members - Request Body:
{ "role_name": "ROOT" | "ADMIN" | "VIEWER" }
- Required Permissions: Team access +
Email Agent
POST /api/teams/{team_id}/email-agent- Configure email agent for a team- Required Permissions: Team access +
email_agent:configure - Request Body:
{ "inbox_email": "string", "ms_client_id": "string", "ms_client_secret": "string", "ms_tenant_id": "string" }
- Required Permissions: Team access +