## 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**: 1. **Permission** - Individual action (e.g., `contract:create`, `checklist:edit`) 2. **Role** - Collection of permissions (ROOT, ADMIN, VIEWER) 3. **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 `Permission` table - roles stored in `Role` table - roles has many-to-many relationship with `Permission` table - 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](https://app.eraser.io/workspace/ZSPoTCtYZRIwToc57lyO?origin=share)** ### **Database Schema** ```python class Organization(): name = models.CharField(max_length=200) email = models.EmailField() ``` ```python 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 ```python 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 ```python 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 `system` roles i.e. "ROOT", "ADMIN", "VIEWER") - `system_roles` created automatically with standard permissions, but can be customized per organization **Team**: workspace within an organization ```python 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 ```python 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:analyze` - `team:view`, `team:create`, `team:edit`, `team:delete`, `team:manage_members` - `checklist:view`, `checklist:create`, `checklist:edit`, `checklist:delete` - `email_agent:view`, `email_agent:configure`, `email_agent:enable`, `email_agent:disable` ## permission verifications ```python 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** ```python @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) - `GET /api/teams/{team_id}/dashboard/stats` - Get team dashboard statistics - **Required Permissions:** Team access + `contract:view` ### **Checklists** - `GET /api/teams/{team_id}/checklists` - List all checklists for a team - **Required Permissions:** Team access + `checklist:view` ### **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": {} }` - `PUT /api/teams/{team_id}` - Update team settings - **Required Permissions:** Team access + `team:edit` - **Request Body:** `{ "name": "string", "description": "string", "settings": {} }` - `DELETE /api/teams/{team_id}` - Delete a team (soft delete) - **Required Permissions:** Team access + `team:delete` - `GET /api/teams/{team_id}/members` - List all team members with their roles - **Required Permissions:** Team access + `team:view` - `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" }` - `DELETE /api/teams/{team_id}/members/{user_id}` - Remove a user from a team - **Required Permissions:** Team access + `team:manage_members` - `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" }` ### **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" }`