GT AI OS Community Edition v2.0.33

Security hardening release addressing CodeQL and Dependabot alerts:

- Fix stack trace exposure in error responses
- Add SSRF protection with DNS resolution checking
- Implement proper URL hostname validation (replaces substring matching)
- Add centralized path sanitization to prevent path traversal
- Fix ReDoS vulnerability in email validation regex
- Improve HTML sanitization in validation utilities
- Fix capability wildcard matching in auth utilities
- Update glob dependency to address CVE
- Add CodeQL suppression comments for verified false positives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
HackWeasel
2025-12-12 17:04:45 -05:00
commit b9dfb86260
746 changed files with 232071 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
"""
Collaboration Team Models for GT 2.0 Tenant Backend
Pydantic models for user collaboration teams (team sharing system).
This is separate from the tenant isolation 'tenants' table (formerly 'teams').
Database Schema:
- teams: User collaboration groups within a tenant
- team_memberships: Team members with two-tier permissions
"""
from datetime import datetime
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field, ConfigDict, field_validator
class TeamBase(BaseModel):
"""Base team model with common fields"""
name: str = Field(..., min_length=1, max_length=255, description="Team name")
description: Optional[str] = Field(None, description="Team description")
class TeamCreate(TeamBase):
"""Model for creating a new team"""
pass
class TeamUpdate(BaseModel):
"""Model for updating a team"""
name: Optional[str] = Field(None, min_length=1, max_length=255)
description: Optional[str] = None
class TeamMember(BaseModel):
"""Team member with permissions"""
id: str = Field(..., description="Membership UUID")
team_id: str = Field(..., description="Team UUID")
user_id: str = Field(..., description="User UUID")
user_email: str = Field(..., description="User email")
user_name: str = Field(..., description="User display name")
team_permission: str = Field(..., description="Team-level permission: 'read', 'share', or 'manager'")
resource_permissions: Dict[str, str] = Field(default_factory=dict, description="Resource-level permissions JSONB")
is_owner: bool = Field(default=False, description="Whether this member is the team owner")
is_observable: bool = Field(default=False, description="Member consents to activity observation")
observable_consent_status: str = Field(default="none", description="Consent status: 'none', 'pending', 'approved', 'revoked'")
observable_consent_at: Optional[str] = Field(None, description="When Observable status was approved")
status: str = Field(default="accepted", description="Membership status: 'pending', 'accepted', or 'declined'")
invited_at: Optional[str] = None
responded_at: Optional[str] = None
joined_at: Optional[str] = None
created_at: Optional[str] = None
updated_at: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class Team(TeamBase):
"""Complete team model with metadata"""
id: str = Field(..., description="Team UUID")
tenant_id: str = Field(..., description="Tenant UUID")
owner_id: str = Field(..., description="Owner user UUID")
owner_name: Optional[str] = Field(None, description="Owner display name")
owner_email: Optional[str] = Field(None, description="Owner email")
is_owner: bool = Field(..., description="Whether current user is the owner")
can_manage: bool = Field(..., description="Whether current user can manage the team")
user_permission: Optional[str] = Field(None, description="Current user's team permission: 'read' or 'share' (None if owner)")
member_count: int = Field(0, description="Number of team members")
shared_resource_count: int = Field(0, description="Number of shared resources (agents and datasets)")
created_at: Optional[str] = None
updated_at: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class TeamWithMembers(Team):
"""Team with full member list"""
members: List[TeamMember] = Field(default_factory=list, description="List of team members")
class TeamListResponse(BaseModel):
"""Response model for listing teams"""
data: List[Team]
total: int
model_config = ConfigDict(from_attributes=True)
class TeamResponse(BaseModel):
"""Response model for single team operation"""
data: Team
model_config = ConfigDict(from_attributes=True)
class TeamWithMembersResponse(BaseModel):
"""Response model for team with members"""
data: TeamWithMembers
model_config = ConfigDict(from_attributes=True)
# Team Membership Models
class AddMemberRequest(BaseModel):
"""Request model for adding a member to a team"""
user_email: str = Field(..., description="Email of user to add")
team_permission: str = Field("read", description="Team permission: 'read', 'share', or 'manager'")
class UpdateMemberPermissionRequest(BaseModel):
"""Request model for updating member permission"""
team_permission: str = Field(..., description="New permission: 'read', 'share', or 'manager'")
@field_validator('team_permission')
@classmethod
def validate_permission(cls, v: str) -> str:
if v not in ["read", "share", "manager"]:
raise ValueError(f"Invalid permission: {v}. Must be 'read', 'share', or 'manager'")
return v
class MemberListResponse(BaseModel):
"""Response model for listing team members"""
data: List[TeamMember]
total: int
model_config = ConfigDict(from_attributes=True)
class MemberResponse(BaseModel):
"""Response model for single member operation"""
data: TeamMember
model_config = ConfigDict(from_attributes=True)
# Team Invitation Models
class TeamInvitation(BaseModel):
"""Pending team invitation"""
id: str = Field(..., description="Invitation (membership) UUID")
team_id: str = Field(..., description="Team UUID")
team_name: str = Field(..., description="Team name")
team_description: Optional[str] = Field(None, description="Team description")
owner_name: str = Field(..., description="Team owner display name")
owner_email: str = Field(..., description="Team owner email")
team_permission: str = Field(..., description="Invited permission: 'read', 'share', or 'manager'")
observable_requested: bool = Field(default=False, description="Whether Observable access was requested on invite")
invited_at: str = Field(..., description="Invitation timestamp")
model_config = ConfigDict(from_attributes=True)
class InvitationActionRequest(BaseModel):
"""Request to accept or decline invitation"""
action: str = Field(..., description="Action: 'accept' or 'decline'")
class InvitationListResponse(BaseModel):
"""Response model for listing invitations"""
data: List[TeamInvitation]
total: int
model_config = ConfigDict(from_attributes=True)
# Resource Sharing Models
class ShareResourceRequest(BaseModel):
"""Request model for sharing a resource to team"""
resource_type: str = Field(..., description="Resource type: 'agent' or 'dataset'")
resource_id: str = Field(..., description="Resource UUID")
user_permissions: Dict[str, str] = Field(
...,
description="User permissions: {user_id: 'read'|'edit'}"
)
class SharedResource(BaseModel):
"""Model for a shared resource"""
resource_type: str = Field(..., description="Resource type: 'agent' or 'dataset'")
resource_id: str = Field(..., description="Resource UUID")
resource_name: str = Field(..., description="Resource name")
resource_owner: str = Field(..., description="Resource owner name or email")
user_permissions: Dict[str, str] = Field(..., description="User permissions map")
class SharedResourcesResponse(BaseModel):
"""Response model for listing shared resources"""
data: List[SharedResource]
total: int
model_config = ConfigDict(from_attributes=True)
# Observable Request Models
class ObservableRequest(BaseModel):
"""Observable access request for a team member"""
team_id: str = Field(..., description="Team UUID")
team_name: str = Field(..., description="Team name")
requested_by_name: str = Field(..., description="Name of manager/owner who requested")
requested_by_email: str = Field(..., description="Email of manager/owner who requested")
requested_at: str = Field(..., description="When request was made")
model_config = ConfigDict(from_attributes=True)
class ObservableRequestListResponse(BaseModel):
"""Response model for listing Observable requests"""
data: List[ObservableRequest]
total: int
model_config = ConfigDict(from_attributes=True)
# Team Activity Models
class TeamActivityMetrics(BaseModel):
"""Team activity metrics for Observable members"""
team_id: str
team_name: str
date_range_days: int
observable_member_count: int
total_member_count: int
team_totals: Dict[str, Any] = Field(
default_factory=dict,
description="Aggregated metrics: conversations, messages, tokens"
)
member_breakdown: List[Dict[str, Any]] = Field(
default_factory=list,
description="Per-member activity stats"
)
time_series: List[Dict[str, Any]] = Field(
default_factory=list,
description="Activity over time"
)
model_config = ConfigDict(from_attributes=True)
class TeamActivityResponse(BaseModel):
"""Response model for team activity"""
data: TeamActivityMetrics
model_config = ConfigDict(from_attributes=True)
# Error Response Models
class ErrorDetail(BaseModel):
"""Error detail model"""
message: str
field: Optional[str] = None
code: Optional[str] = None
class ErrorResponse(BaseModel):
"""Error response model"""
error: str
details: Optional[List[ErrorDetail]] = None
model_config = ConfigDict(from_attributes=True)