diff --git a/backend/src/xfd_django/xfd_api/views.py b/backend/src/xfd_django/xfd_api/views.py index 79f33800..f450a9db 100644 --- a/backend/src/xfd_django/xfd_api/views.py +++ b/backend/src/xfd_django/xfd_api/views.py @@ -102,20 +102,8 @@ async def get_redis_client(request: Request): return request.app.state.redis -# Healthcheck endpoint -@api_router.get("/healthcheck", tags=["Testing"]) -async def healthcheck(): - """ - Healthcheck endpoint. - - Returns: - dict: A dictionary containing the health status of the application. - """ - return {"status": "ok"} - - # ======================================== -# Proxy Endpoints +# Analytic Endpoints # ======================================== @@ -160,7 +148,7 @@ async def matomo_proxy( @api_router.api_route( "/pe/{path:path}", dependencies=[Depends(get_current_active_user)], - tags=["P&E Proxy"], + tags=["Analytics"], ) async def pe_proxy( path: str, request: Request, current_user: User = Depends(get_current_active_user) @@ -175,7 +163,59 @@ async def pe_proxy( # ======================================== -# CPE/CVE Endpoints +# API Key Endpoints +# ======================================== + + +# POST +@api_router.post("/api-keys", response_model=ApiKeySchema, tags=["API Keys"]) +async def create_api_key(current_user: User = Depends(get_current_active_user)): + """Create api key.""" + return api_key_methods.post(current_user) + + +# DELETE +@api_router.delete("/api-keys/{id}", tags=["API Keys"]) +async def delete_api_key( + id: str, current_user: User = Depends(get_current_active_user) +): + """Delete api key by id.""" + return api_key_methods.delete(id, current_user) + + +# ======================================== +# Auth Endpoints +# ======================================== + + +# Okta Callback +@api_router.post("/auth/okta-callback", tags=["Auth"]) +async def okta_callback(request: Request): + """Handle Okta Callback.""" + return await auth_methods.handle_okta_callback(request) + + +# Login +@api_router.get("/login", tags=["Auth"]) +async def login_route(): + """Handle V1 Login.""" + return login() + + +# V1 Callback +@api_router.post("/auth/callback", tags=["Auth"]) +async def callback_route(request: Request): + """Handle V1 Callback.""" + body = await request.json() + try: + user_info = callback(body) + return user_info + except Exception as error: + raise HTTPException(status_code=400, detail=str(error)) + + +# ======================================== +# CPE Endpoints # ======================================== @@ -183,7 +223,7 @@ async def pe_proxy( "/cpes/{cpe_id}", # dependencies=[Depends(get_current_active_user)], response_model=CpeSchema, - tags=["Cpe"], + tags=["CPEs"], ) async def call_get_cpes_by_id(cpe_id): """ @@ -194,11 +234,16 @@ async def call_get_cpes_by_id(cpe_id): return get_cpes_by_id(cpe_id) +# ======================================== +# CVE Endpoints +# ======================================== + + @api_router.get( "/cves/{cve_id}", # dependencies=[Depends(get_current_active_user)], response_model=CveSchema, - tags=["Cve"], + tags=["CVEs"], ) async def call_get_cves_by_id(cve_id): """ @@ -213,7 +258,7 @@ async def call_get_cves_by_id(cve_id): "/cves/name/{cve_name}", # dependencies=[Depends(get_current_active_user)], response_model=CveSchema, - tags=["Get cve by name"], + tags=["CVEs"], ) async def call_get_cves_by_name(cve_name): """ @@ -260,7 +305,7 @@ async def call_export_domains( "/domain/{domain_id}", dependencies=[Depends(get_current_active_user)], response_model=GetDomainResponse, - tags=["Get domain by id"], + tags=["Domains"], ) async def call_get_domain_by_id(domain_id: str): """Get domain by id.""" @@ -268,315 +313,293 @@ async def call_get_domain_by_id(domain_id: str): # ======================================== -# Vulnerability Endpoints +# Notification Endpoints # ======================================== +# POST @api_router.post( - "/vulnerabilities/search", - dependencies=[Depends(get_current_active_user)], - response_model=VulnerabilitySearchResponse, - tags=["Vulnerabilities"], + "/notifications", response_model=NotificationSchema, tags=["Notifications"] ) -async def call_search_vulnerabilities( - vulnerability_search: VulnerabilitySearch, - current_user: User = Depends(get_current_active_user), -): - """Search vulnerabilities.""" - vulnerabilities, count = search_vulnerabilities(vulnerability_search, current_user) - - if vulnerability_search.groupBy: - # Handle grouped results appropriately - return VulnerabilitySearchResponse(result=vulnerabilities, count=count) - - try: - # Convert each ORM instance to a Pydantic model - result = [GetVulnerabilityResponse.model_validate(v) for v in vulnerabilities] - except Exception as e: - raise HTTPException(status_code=500, detail=f"Serialization error: {str(e)}") - - return VulnerabilitySearchResponse(result=result, count=count) +async def create_notification(current_user: User = Depends(get_current_active_user)): + """Create notification key.""" + # return notification_handler.post(current_user) + return [] -@api_router.post( - "/vulnerabilities/export", - dependencies=[Depends(get_current_active_user)], - tags=["Vulnerabilities"], +# DELETE +@api_router.delete( + "/notifications/{id}", response_model=NotificationSchema, tags=["Notifications"] ) -async def get_export_vulnerabilities( - vulnerability_search: VulnerabilitySearch, - current_user: User = Depends(get_current_active_user), +async def delete_notification( + id: str, current_user: User = Depends(get_current_active_user) ): - """Export vulnerabilities.""" - return export_vulnerabilities(vulnerability_search, current_user) + """Delete notification by id.""" + return notification_methods.delete(id, current_user) +# GET ALL: Doesn't require authentication @api_router.get( - "/vulnerabilities/{vulnerability_id}", - dependencies=[Depends(get_current_active_user)], - response_model=GetVulnerabilityResponse, - tags=["Vulnerabilities"], + "/notifications", response_model=List[NotificationSchema], tags=["Notifications"] ) -async def call_get_vulnerability_by_id( - vulnerability_id, current_user: User = Depends(get_current_active_user) -): - """Get vulnerability by id.""" - return get_vulnerability_by_id(vulnerability_id, current_user) +async def get_all_notifications(): + """Get all notifications.""" + return notification_methods.get_all() -@api_router.put( - "/vulnerabilities/{vulnerability_id}", - dependencies=[Depends(get_current_active_user)], - response_model=VulnerabilitySchema, - tags="Update vulnerability", +# GET BY ID +@api_router.get( + "/notifications/{id}", response_model=NotificationSchema, tags=["Notifications"] ) -async def call_update_vulnerability( - vulnerability_id, - data: VulnerabilitySchema, - current_user: User = Depends(get_current_active_user), +async def get_notification( + id: str, current_user: User = Depends(get_current_active_user) ): - """ - Update vulnerability by id. - - Returns: - object: a single vulnerability object that has been modified. - """ - return update_vulnerability(vulnerability_id, data, current_user) - - -# ======================================== -# Auth Endpoints -# ======================================== - - -# Okta Callback -@api_router.post("/auth/okta-callback", tags=["auth"]) -async def okta_callback(request: Request): - """Handle Okta Callback.""" - return await auth_methods.handle_okta_callback(request) + """Get notification by id.""" + return notification_methods.get_by_id(id, current_user) -# Login -@api_router.get("/login", tags=["auth"]) -async def login_route(): - """Handle V1 Login.""" - return login() +# UPDATE BY ID +@api_router.put("/notifications/{id}", tags=["Notifications"]) +async def update_notification( + id: str, current_user: User = Depends(get_current_active_user) +): + """Update notification key by id.""" + return notification_methods.delete(id, current_user) -# V1 Callback -@api_router.post("/auth/callback", tags=["auth"]) -async def callback_route(request: Request): - """Handle V1 Callback.""" - body = await request.json() - try: - user_info = callback(body) - return user_info - except Exception as error: - raise HTTPException(status_code=400, detail=str(error)) +# GET 508 Banner: Doesn't require authentication +@api_router.get("/notifications/508-banner", tags=["Notifications"]) +async def get_508_banner(): + """Get notification by id.""" + return notification_methods.get_508_banner() # ======================================== -# Users Endpoints +# Organization Endpoints # ======================================== -@api_router.post( - "/users/me/acceptTerms", - response_model=UserSchema, +@api_router.get( + "/organizations", dependencies=[Depends(get_current_active_user)], - tags=["Users"], + response_model=List[OrganizationSchema.GetOrganizationSchema], + tags=["Organizations"], ) -async def call_accept_terms( - version_data: VersionModel, current_user: User = Depends(get_current_active_user) -): - """Accept the latest terms of service.""" - - return accept_terms(version_data, current_user) +async def list_organizations(current_user: User = Depends(get_current_active_user)): + """Retrieve a list of all organizations.""" + return organization.list_organizations(current_user) -@api_router.get("/users/me", tags=["Users"]) -async def read_users_me(current_user: User = Depends(get_current_active_user)): - return get_me(current_user) +@api_router.get( + "/organizations/tags", + dependencies=[Depends(get_current_active_user)], + response_model=List[OrganizationSchema.GetTagSchema], + tags=["Organizations"], +) +async def get_organization_tags(current_user: User = Depends(get_current_active_user)): + """Retrieve a list of organization tags.""" + return organization.get_tags(current_user) -@api_router.delete( - "/users/{userId}", - response_model=OrganizationSchema.GenericMessageResponseModel, +@api_router.get( + "/organizations/{organization_id}", dependencies=[Depends(get_current_active_user)], - tags=["Users"], + response_model=OrganizationSchema.GetSingleOrganizationSchema, + tags=["Organizations"], ) -async def call_delete_user( - userId: str, current_user: User = Depends(get_current_active_user) +async def get_organization( + organization_id: str, current_user: User = Depends(get_current_active_user) ): - """Delete user.""" - return delete_user(userId, current_user) + """Retrieve an organization by its ID.""" + return organization.get_organization(organization_id, current_user) @api_router.get( - "/users", - response_model=List[UserResponseV2], + "/organizations/state/{state}", dependencies=[Depends(get_current_active_user)], - tags=["Users"], + response_model=List[OrganizationSchema.GetOrganizationSchema], + tags=["Organizations"], ) -async def call_get_users(current_user: User = Depends(get_current_active_user)): - """Get all users.""" - return get_users(current_user) +async def get_organizations_by_state( + state: str, current_user: User = Depends(get_current_active_user) +): + """Retrieve organizations by state.""" + return organization.get_by_state(state, current_user) @api_router.get( - "/users/regionId/{regionId}", - response_model=List[UserSchema], + "/organizations/regionId/{region_id}", dependencies=[Depends(get_current_active_user)], - tags=["Users"], + response_model=List[OrganizationSchema.GetOrganizationSchema], + tags=["Organizations"], ) -async def call_get_users_by_region_id( - regionId, current_user: User = Depends(get_current_active_user) +async def get_organizations_by_region( + region_id: str, current_user: User = Depends(get_current_active_user) ): - """ - Call get_users_by_region_id() - Args: - request : The HTTP request containing query parameters. + """Retrieve organizations by region ID.""" + return organization.get_by_region(region_id, current_user) - Raises: - HTTPException: If the user is not authorized or no users are found. - Returns: - List[User]: A list of users matching the filter criteria. - """ - return get_users_by_region_id(regionId, current_user) +@api_router.post( + "/organizations", + dependencies=[Depends(get_current_active_user)], + response_model=OrganizationSchema.GetSingleOrganizationSchema, + tags=["Organizations"], +) +async def create_organization( + organization_data: OrganizationSchema.NewOrganization, + current_user: User = Depends(get_current_active_user), +): + """Create a new organization.""" + return organization.create_organization(organization_data, current_user) -@api_router.get( - "/users/state/{state}", - response_model=List[UserSchema], +@api_router.post( + "/organizations_upsert", dependencies=[Depends(get_current_active_user)], - tags=["Users"], + response_model=OrganizationSchema.GetSingleOrganizationSchema, + tags=["Organizations"], ) -async def call_get_users_by_state( - state, current_user: User = Depends(get_current_active_user) +async def upsert_organization( + organization_data: OrganizationSchema.NewOrganization, + current_user: User = Depends(get_current_active_user), ): - """ - Call get_users_by_state() - Args: - request : The HTTP request containing query parameters. + """Upsert an organization.""" + return organization.upsert_organization(organization_data, current_user) - Raises: - HTTPException: If the user is not authorized or no users are found. - Returns: - List[User]: A list of users matching the filter criteria. - """ - return get_users_by_state(state, current_user) +@api_router.put( + "/organizations/{organization_id}", + dependencies=[Depends(get_current_active_user)], + response_model=OrganizationSchema.GetSingleOrganizationSchema, + tags=["Organizations"], +) +async def update_organization( + organization_id: str, + org_data: OrganizationSchema.NewOrganization, + current_user: User = Depends(get_current_active_user), +): + """Update an organization by its ID.""" + return organization.update_organization(organization_id, org_data, current_user) -@api_router.get( - "/v2/users", - response_model=List[UserResponseV2], +@api_router.delete( + "/organizations/{organization_id}", dependencies=[Depends(get_current_active_user)], - tags=["Users"], + response_model=OrganizationSchema.GenericMessageResponseModel, + tags=["Organizations"], ) -async def call_get_users_v2( - state: Optional[str] = Query(None), - regionId: Optional[str] = Query(None), - invitePending: Optional[bool] = Query(None), - current_user: User = Depends(get_current_active_user), +async def delete_organization( + organization_id: str, current_user: User = Depends(get_current_active_user) ): - """Get users with filter.""" - return get_users_v2(state, regionId, invitePending, current_user) + """Delete an organization by its ID.""" + return organization.delete_organization(organization_id, current_user) -@api_router.put( - "/v2/users/{user_id}", +@api_router.post( + "/v2/organizations/{organization_id}/users", dependencies=[Depends(get_current_active_user)], - response_model=UserResponseV2, - tags=["Users"], + tags=["Organizations"], ) -async def update_user_v2_view( - user_id: str, - user_data: UpdateUserV2, +async def add_user_to_organization_v2( + organization_id: str, + user_data: OrganizationSchema.NewOrgUser, current_user: User = Depends(get_current_active_user), ): - """Update a particular user.""" - return update_user_v2(user_id, user_data, current_user) + """Add a user to an organization.""" + return organization.add_user_to_org_v2(organization_id, user_data, current_user) -@api_router.post("/users/{userId}", tags=["Users"]) -async def call_update_user( - userId, body, current_user: User = Depends(get_current_active_user) +@api_router.post( + "/organizations/{organization_id}/roles/{role_id}/approve", + dependencies=[Depends(get_current_active_user)], + response_model=OrganizationSchema.GenericMessageResponseModel, + tags=["Organizations"], +) +async def approve_role( + organization_id: str, + role_id: str, + current_user: User = Depends(get_current_active_user), ): - """ - Update a user by ID. - Args: - userId : The ID of the user to update. - request : The HTTP request containing authorization and target for update. + """Approve a role within an organization.""" + return organization.approve_role(organization_id, role_id, current_user) - Raises: - HTTPException: If the user is not authorized or the user is not found. - Returns: - JSONResponse: The result of the update. - """ - return update_user(userId, body, current_user) +@api_router.post( + "/organizations/{organization_id}/roles/{role_id}/remove", + dependencies=[Depends(get_current_active_user)], + response_model=OrganizationSchema.GenericMessageResponseModel, + tags=["Organizations"], +) +async def remove_role( + organization_id: str, + role_id: str, + current_user: User = Depends(get_current_active_user), +): + """Remove a role from an organization.""" + return organization.remove_role(organization_id, role_id, current_user) -@api_router.put( - "/users/{user_id}/register/approve", +@api_router.post( + "/organizations/{organization_id}/granularScans/{scan_id}/update", dependencies=[Depends(get_current_active_user)], - response_model=RegisterUserResponse, - tags=["Users"], + response_model=OrganizationSchema.GetSingleOrganizationSchema, + tags=["Organizations"], ) -async def register_approve( - user_id: str, current_user: User = Depends(get_current_active_user) +async def update_granular_scan( + organization_id: str, + scan_id: str, + scan_data: OrganizationSchema.NewOrgScan, + current_user: User = Depends(get_current_active_user), ): - """Approve a registered user.""" - return user.approve_user_registration(user_id, current_user) + """Update a granular scan for an organization.""" + return organization.update_org_scan( + organization_id, scan_id, scan_data, current_user + ) -@api_router.put( - "/users/{user_id}/register/deny", +@api_router.get( + "/v2/organizations", dependencies=[Depends(get_current_active_user)], - response_model=RegisterUserResponse, - tags=["Users"], + response_model=List[OrganizationSchema.GetOrganizationSchema], + tags=["Organizations"], ) -async def register_deny( - user_id: str, current_user: User = Depends(get_current_active_user) +async def list_organizations_v2( + state: Optional[List[str]] = Query(None), + regionId: Optional[List[str]] = Query(None), + current_user: User = Depends(get_current_active_user), ): - """Deny a registered user.""" - return user.deny_user_registration(user_id, current_user) + """Retrieve a list of all organizations (version 2).""" + return organization.list_organizations_v2(state, regionId, current_user) @api_router.post( - "/users", + "/search/organizations", dependencies=[Depends(get_current_active_user)], - response_model=NewUserResponseModel, - tags=["Users"], + tags=["Organizations"], ) -async def invite_user( - new_user: NewUser, current_user: User = Depends(get_current_active_user) +async def search_organizations( + search_body: OrganizationSchema.OrganizationSearchBody, + current_user: User = Depends(get_current_active_user), ): - """Invite a user.""" - return user.invite(new_user, current_user) + """Search for organizations in Elasticsearch.""" + return organization.search_organizations_task(search_body, current_user) # ======================================== -# Api-Key Endpoints +# Region Endpoints # ======================================== -# POST -@api_router.post("/api-keys", response_model=ApiKeySchema, tags=["api-keys"]) -async def create_api_key(current_user: User = Depends(get_current_active_user)): - """Create api key.""" - return api_key_methods.post(current_user) - - -# DELETE -@api_router.delete("/api-keys/{id}", tags=["api-keys"]) -async def delete_api_key( - id: str, current_user: User = Depends(get_current_active_user) -): - """Delete api key by id.""" - return api_key_methods.delete(id, current_user) +@api_router.get( + "/regions", + dependencies=[Depends(get_current_active_user)], + response_model=List[OrganizationSchema.RegionSchema], + tags=["Regions"], +) +async def list_regions(current_user: User = Depends(get_current_active_user)): + """Retrieve a list of all regions.""" + return organization.get_all_regions(current_user) # ======================================== @@ -589,7 +612,7 @@ async def delete_api_key( "/saved-searches", dependencies=[Depends(get_current_active_user)], response_model=SavedSearchSchema, - tags=["Saved Search"], + tags=["Saved Searches"], ) async def call_create_saved_search( saved_search: SavedSearchCreate, @@ -616,7 +639,7 @@ async def call_create_saved_search( "/saved-searches", dependencies=[Depends(get_current_active_user)], response_model=SavedSearchList, - tags=["Saved Search"], + tags=["Saved Searches"], ) async def call_list_saved_searches(user: User = Depends(get_current_active_user)): """Retrieve a list of all saved searches.""" @@ -628,7 +651,7 @@ async def call_list_saved_searches(user: User = Depends(get_current_active_user) "/saved-searches/{saved_search_id}", dependencies=[Depends(get_current_active_user)], response_model=SavedSearchSchema, - tags=["Saved Search"], + tags=["Saved Searches"], ) async def call_get_saved_search( saved_search_id: str, current_user: User = Depends(get_current_active_user) @@ -642,7 +665,7 @@ async def call_get_saved_search( "/saved-searches/{saved_search_id}", dependencies=[Depends(get_current_active_user)], response_model=SavedSearchUpdate, - tags=["Saved Search"], + tags=["Saved Searches"], ) async def call_update_saved_search( saved_search: SavedSearchUpdate, @@ -669,7 +692,7 @@ async def call_update_saved_search( @api_router.delete( "/saved-searches/{saved_search_id}", dependencies=[Depends(get_current_active_user)], - tags=["Saved Search"], + tags=["Saved Searches"], ) async def call_delete_saved_search( saved_search_id: str, current_user: User = Depends(get_current_active_user) @@ -679,119 +702,57 @@ async def call_delete_saved_search( # GET ALL -@api_router.get("/api-keys", response_model=List[ApiKeySchema], tags=["api-keys"]) +@api_router.get("/api-keys", response_model=List[ApiKeySchema], tags=["API Keys"]) async def get_all_api_keys(current_user: User = Depends(get_current_active_user)): """Get all api keys.""" return api_key_methods.get_all(current_user) # GET BY ID -@api_router.get("/api-keys/{id}", response_model=ApiKeySchema, tags=["api-keys"]) +@api_router.get("/api-keys/{id}", response_model=ApiKeySchema, tags=["API Keys"]) async def get_api_key(id: str, current_user: User = Depends(get_current_active_user)): """Get api key by id.""" return api_key_methods.get_by_id(id, current_user) # ======================================== -# Notification Endpoints +# Scan Endpoints # ======================================== -# POST -@api_router.post( - "/notifications", response_model=NotificationSchema, tags=["notifications"] -) -async def create_notification(current_user: User = Depends(get_current_active_user)): - """Create notification key.""" - # return notification_handler.post(current_user) - return [] - - -# DELETE -@api_router.delete( - "/notifications/{id}", response_model=NotificationSchema, tags=["notifications"] +@api_router.get( + "/scans", + dependencies=[Depends(get_current_active_user)], + response_model=scanSchema.GetScansResponseModel, + tags=["Scans"], ) -async def delete_notification( - id: str, current_user: User = Depends(get_current_active_user) -): - """Delete notification by id.""" - return notification_methods.delete(id, current_user) +async def list_scans(current_user: User = Depends(get_current_active_user)): + """Retrieve a list of all scans.""" + return scan.list_scans(current_user) -# GET ALL: Doesn't require authentication @api_router.get( - "/notifications", response_model=List[NotificationSchema], tags=["notifications"] + "/granularScans", + dependencies=[Depends(get_current_active_user)], + response_model=scanSchema.GetGranularScansResponseModel, + tags=["Scans"], ) -async def get_all_notifications(): - """Get all notifications.""" - return notification_methods.get_all() +async def list_granular_scans(current_user: User = Depends(get_current_active_user)): + """Retrieve a list of granular scans. User must be authenticated.""" + return scan.list_granular_scans(current_user) -# GET BY ID -@api_router.get( - "/notifications/{id}", response_model=NotificationSchema, tags=["notifications"] +@api_router.post( + "/scans", + dependencies=[Depends(get_current_active_user)], + response_model=scanSchema.CreateScanResponseModel, + tags=["Scans"], ) -async def get_notification( - id: str, current_user: User = Depends(get_current_active_user) +async def create_scan( + scan_data: scanSchema.NewScan, current_user: User = Depends(get_current_active_user) ): - """Get notification by id.""" - return notification_methods.get_by_id(id, current_user) - - -# UPDATE BY ID -@api_router.put("/notifications/{id}", tags=["notifications"]) -async def update_notification( - id: str, current_user: User = Depends(get_current_active_user) -): - """Update notification key by id.""" - return notification_methods.delete(id, current_user) - - -# GET 508 Banner: Doesn't require authentication -@api_router.get("/notifications/508-banner", tags=["notifications"]) -async def get_508_banner(): - """Get notification by id.""" - return notification_methods.get_508_banner() - - -# ======================================== -# Scan Endpoints -# ======================================== - - -@api_router.get( - "/scans", - dependencies=[Depends(get_current_active_user)], - response_model=scanSchema.GetScansResponseModel, - tags=["Scans"], -) -async def list_scans(current_user: User = Depends(get_current_active_user)): - """Retrieve a list of all scans.""" - return scan.list_scans(current_user) - - -@api_router.get( - "/granularScans", - dependencies=[Depends(get_current_active_user)], - response_model=scanSchema.GetGranularScansResponseModel, - tags=["Scans"], -) -async def list_granular_scans(current_user: User = Depends(get_current_active_user)): - """Retrieve a list of granular scans. User must be authenticated.""" - return scan.list_granular_scans(current_user) - - -@api_router.post( - "/scans", - dependencies=[Depends(get_current_active_user)], - response_model=scanSchema.CreateScanResponseModel, - tags=["Scans"], -) -async def create_scan( - scan_data: scanSchema.NewScan, current_user: User = Depends(get_current_active_user) -): - """Create a new scan.""" - return scan.create_scan(scan_data, current_user) + """Create a new scan.""" + return scan.create_scan(scan_data, current_user) @api_router.get( @@ -854,7 +815,87 @@ async def invoke_scheduler(current_user: User = Depends(get_current_active_user) # ======================================== -# Stats Endpoints +# Scan Task Endpoints +# ======================================== + + +@api_router.post( + "/scan-tasks/search", + dependencies=[Depends(get_current_active_user)], + response_model=scanTaskSchema.ScanTaskListResponse, + tags=["Scan Tasks"], +) +async def list_scan_tasks( + search_data: Optional[scanTaskSchema.ScanTaskSearch] = Body(None), + current_user: User = Depends(get_current_active_user), +): + """List scan tasks based on filters.""" + return scan_tasks.list_scan_tasks(search_data, current_user) + + +@api_router.post( + "/scan-tasks/{scan_task_id}/kill", + dependencies=[Depends(get_current_active_user)], + tags=["Scan Tasks"], +) +async def kill_scan_tasks( + scan_task_id: UUID, current_user: User = Depends(get_current_active_user) +): + """Kill a scan task.""" + return scan_tasks.kill_scan_task(scan_task_id, current_user) + + +@api_router.get( + "/scan-tasks/{scan_task_id}/logs", + dependencies=[Depends(get_current_active_user)], + # response_model=scanTaskSchema.GenericResponse, + tags=["Scan Tasks"], +) +async def get_scan_task_logs( + scan_task_id: UUID, current_user: User = Depends(get_current_active_user) +): + """Get logs from a particular scan task.""" + return scan_tasks.get_scan_task_logs(scan_task_id, current_user) + + +# ======================================== +# Search Endpoints +# ======================================== + + +@api_router.post( + "/search", + dependencies=[Depends(get_current_active_user)], + response_model=SearchResponse, + tags=["Search"], +) +async def search( + search_body: DomainSearchBody, current_user: User = Depends(get_current_active_user) +): + """ElasticSearch get domains index.""" + try: + return await search_post(search_body, current_user) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) + ) + + +@api_router.post( + "/search/export", dependencies=[Depends(get_current_active_user)], tags=["Search"] +) +async def export_endpoint( + search_body: DomainSearchBody, current_user: User = Depends(get_current_active_user) +): + try: + result = await search_export(search_body, current_user) + return result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +# ======================================== +# Stat Endpoints # ======================================== @@ -983,303 +1024,277 @@ async def get_by_org( # ======================================== -# Scan Task Endpoints +# Testing Endpoints # ======================================== -@api_router.post( - "/scan-tasks/search", - dependencies=[Depends(get_current_active_user)], - response_model=scanTaskSchema.ScanTaskListResponse, - tags=["Scan Tasks"], -) -async def list_scan_tasks( - search_data: Optional[scanTaskSchema.ScanTaskSearch] = Body(None), - current_user: User = Depends(get_current_active_user), -): - """List scan tasks based on filters.""" - return scan_tasks.list_scan_tasks(search_data, current_user) +# Healthcheck endpoint +@api_router.get("/healthcheck", tags=["Testing"]) +async def healthcheck(): + """ + Healthcheck endpoint. + Returns: + dict: A dictionary containing the health status of the application. + """ + return {"status": "ok"} -@api_router.post( - "/scan-tasks/{scan_task_id}/kill", - dependencies=[Depends(get_current_active_user)], - tags=["Scan Tasks"], -) -async def kill_scan_tasks( - scan_task_id: UUID, current_user: User = Depends(get_current_active_user) -): - """Kill a scan task.""" - return scan_tasks.kill_scan_task(scan_task_id, current_user) + +# ======================================== +# User Endpoints +# ======================================== -@api_router.get( - "/scan-tasks/{scan_task_id}/logs", +@api_router.post( + "/users/me/acceptTerms", + response_model=UserSchema, dependencies=[Depends(get_current_active_user)], - # response_model=scanTaskSchema.GenericResponse, - tags=["Scan Tasks"], + tags=["Users"], ) -async def get_scan_task_logs( - scan_task_id: UUID, current_user: User = Depends(get_current_active_user) +async def call_accept_terms( + version_data: VersionModel, current_user: User = Depends(get_current_active_user) ): - """Get logs from a particular scan task.""" - return scan_tasks.get_scan_task_logs(scan_task_id, current_user) + """Accept the latest terms of service.""" + return accept_terms(version_data, current_user) -# ======================================== -# Organization Endpoints -# ======================================== + +@api_router.get("/users/me", tags=["Users"]) +async def read_users_me(current_user: User = Depends(get_current_active_user)): + return get_me(current_user) -@api_router.get( - "/organizations", +@api_router.delete( + "/users/{userId}", + response_model=OrganizationSchema.GenericMessageResponseModel, dependencies=[Depends(get_current_active_user)], - response_model=List[OrganizationSchema.GetOrganizationSchema], - tags=["Organizations"], + tags=["Users"], ) -async def list_organizations(current_user: User = Depends(get_current_active_user)): - """Retrieve a list of all organizations.""" - return organization.list_organizations(current_user) +async def call_delete_user( + userId: str, current_user: User = Depends(get_current_active_user) +): + """Delete user.""" + return delete_user(userId, current_user) @api_router.get( - "/organizations/tags", + "/users", + response_model=List[UserResponseV2], dependencies=[Depends(get_current_active_user)], - response_model=List[OrganizationSchema.GetTagSchema], - tags=["Organizations"], + tags=["Users"], ) -async def get_organization_tags(current_user: User = Depends(get_current_active_user)): - """Retrieve a list of organization tags.""" - return organization.get_tags(current_user) +async def call_get_users(current_user: User = Depends(get_current_active_user)): + """Get all users.""" + return get_users(current_user) @api_router.get( - "/organizations/{organization_id}", + "/users/regionId/{regionId}", + response_model=List[UserSchema], dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GetSingleOrganizationSchema, - tags=["Organizations"], + tags=["Users"], ) -async def get_organization( - organization_id: str, current_user: User = Depends(get_current_active_user) +async def call_get_users_by_region_id( + regionId, current_user: User = Depends(get_current_active_user) ): - """Retrieve an organization by its ID.""" - return organization.get_organization(organization_id, current_user) + """ + Call get_users_by_region_id() + Args: + request : The HTTP request containing query parameters. + Raises: + HTTPException: If the user is not authorized or no users are found. -@api_router.get( - "/organizations/state/{state}", - dependencies=[Depends(get_current_active_user)], - response_model=List[OrganizationSchema.GetOrganizationSchema], - tags=["Organizations"], -) -async def get_organizations_by_state( - state: str, current_user: User = Depends(get_current_active_user) -): - """Retrieve organizations by state.""" - return organization.get_by_state(state, current_user) + Returns: + List[User]: A list of users matching the filter criteria. + """ + return get_users_by_region_id(regionId, current_user) @api_router.get( - "/organizations/regionId/{region_id}", + "/users/state/{state}", + response_model=List[UserSchema], dependencies=[Depends(get_current_active_user)], - response_model=List[OrganizationSchema.GetOrganizationSchema], - tags=["Organizations"], + tags=["Users"], ) -async def get_organizations_by_region( - region_id: str, current_user: User = Depends(get_current_active_user) +async def call_get_users_by_state( + state, current_user: User = Depends(get_current_active_user) ): - """Retrieve organizations by region ID.""" - return organization.get_by_region(region_id, current_user) + """ + Call get_users_by_state() + Args: + request : The HTTP request containing query parameters. + Raises: + HTTPException: If the user is not authorized or no users are found. -@api_router.get( - "/regions", - dependencies=[Depends(get_current_active_user)], - response_model=List[OrganizationSchema.RegionSchema], - tags=["Regions"], -) -async def list_regions(current_user: User = Depends(get_current_active_user)): - """Retrieve a list of all regions.""" - return organization.get_all_regions(current_user) + Returns: + List[User]: A list of users matching the filter criteria. + """ + return get_users_by_state(state, current_user) -@api_router.post( - "/organizations", +@api_router.get( + "/v2/users", + response_model=List[UserResponseV2], dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GetSingleOrganizationSchema, - tags=["Organizations"], + tags=["Users"], ) -async def create_organization( - organization_data: OrganizationSchema.NewOrganization, +async def call_get_users_v2( + state: Optional[str] = Query(None), + regionId: Optional[str] = Query(None), + invitePending: Optional[bool] = Query(None), current_user: User = Depends(get_current_active_user), ): - """Create a new organization.""" - return organization.create_organization(organization_data, current_user) + """Get users with filter.""" + return get_users_v2(state, regionId, invitePending, current_user) -@api_router.post( - "/organizations_upsert", +@api_router.put( + "/v2/users/{user_id}", dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GetSingleOrganizationSchema, - tags=["Organizations"], + response_model=UserResponseV2, + tags=["Users"], ) -async def upsert_organization( - organization_data: OrganizationSchema.NewOrganization, +async def update_user_v2_view( + user_id: str, + user_data: UpdateUserV2, current_user: User = Depends(get_current_active_user), ): - """Upsert an organization.""" - return organization.upsert_organization(organization_data, current_user) + """Update a particular user.""" + return update_user_v2(user_id, user_data, current_user) -@api_router.put( - "/organizations/{organization_id}", - dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GetSingleOrganizationSchema, - tags=["Organizations"], -) -async def update_organization( - organization_id: str, - org_data: OrganizationSchema.NewOrganization, - current_user: User = Depends(get_current_active_user), +@api_router.post("/users/{userId}", tags=["Users"]) +async def call_update_user( + userId, body, current_user: User = Depends(get_current_active_user) ): - """Update an organization by its ID.""" - return organization.update_organization(organization_id, org_data, current_user) + """ + Update a user by ID. + Args: + userId : The ID of the user to update. + request : The HTTP request containing authorization and target for update. + Raises: + HTTPException: If the user is not authorized or the user is not found. -@api_router.delete( - "/organizations/{organization_id}", - dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GenericMessageResponseModel, - tags=["Organizations"], -) -async def delete_organization( - organization_id: str, current_user: User = Depends(get_current_active_user) -): - """Delete an organization by its ID.""" - return organization.delete_organization(organization_id, current_user) + Returns: + JSONResponse: The result of the update. + """ + return update_user(userId, body, current_user) -@api_router.post( - "/v2/organizations/{organization_id}/users", +@api_router.put( + "/users/{user_id}/register/approve", dependencies=[Depends(get_current_active_user)], - tags=["Organizations"], + response_model=RegisterUserResponse, + tags=["Users"], ) -async def add_user_to_organization_v2( - organization_id: str, - user_data: OrganizationSchema.NewOrgUser, - current_user: User = Depends(get_current_active_user), +async def register_approve( + user_id: str, current_user: User = Depends(get_current_active_user) ): - """Add a user to an organization.""" - return organization.add_user_to_org_v2(organization_id, user_data, current_user) + """Approve a registered user.""" + return user.approve_user_registration(user_id, current_user) -@api_router.post( - "/organizations/{organization_id}/roles/{role_id}/approve", +@api_router.put( + "/users/{user_id}/register/deny", dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GenericMessageResponseModel, - tags=["Organizations"], + response_model=RegisterUserResponse, + tags=["Users"], ) -async def approve_role( - organization_id: str, - role_id: str, - current_user: User = Depends(get_current_active_user), +async def register_deny( + user_id: str, current_user: User = Depends(get_current_active_user) ): - """Approve a role within an organization.""" - return organization.approve_role(organization_id, role_id, current_user) + """Deny a registered user.""" + return user.deny_user_registration(user_id, current_user) @api_router.post( - "/organizations/{organization_id}/roles/{role_id}/remove", + "/users", dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GenericMessageResponseModel, - tags=["Organizations"], + response_model=NewUserResponseModel, + tags=["Users"], ) -async def remove_role( - organization_id: str, - role_id: str, - current_user: User = Depends(get_current_active_user), +async def invite_user( + new_user: NewUser, current_user: User = Depends(get_current_active_user) ): - """Remove a role from an organization.""" - return organization.remove_role(organization_id, role_id, current_user) + """Invite a user.""" + return user.invite(new_user, current_user) + + +# ======================================== +# Vulnerability Endpoints +# ======================================== @api_router.post( - "/organizations/{organization_id}/granularScans/{scan_id}/update", + "/vulnerabilities/search", dependencies=[Depends(get_current_active_user)], - response_model=OrganizationSchema.GetSingleOrganizationSchema, - tags=["Organizations"], + response_model=VulnerabilitySearchResponse, + tags=["Vulnerabilities"], ) -async def update_granular_scan( - organization_id: str, - scan_id: str, - scan_data: OrganizationSchema.NewOrgScan, +async def call_search_vulnerabilities( + vulnerability_search: VulnerabilitySearch, current_user: User = Depends(get_current_active_user), ): - """Update a granular scan for an organization.""" - return organization.update_org_scan( - organization_id, scan_id, scan_data, current_user - ) + """Search vulnerabilities.""" + vulnerabilities, count = search_vulnerabilities(vulnerability_search, current_user) + if vulnerability_search.groupBy: + # Handle grouped results appropriately + return VulnerabilitySearchResponse(result=vulnerabilities, count=count) -@api_router.get( - "/v2/organizations", - dependencies=[Depends(get_current_active_user)], - response_model=List[OrganizationSchema.GetOrganizationSchema], - tags=["Organizations"], -) -async def list_organizations_v2( - state: Optional[List[str]] = Query(None), - regionId: Optional[List[str]] = Query(None), - current_user: User = Depends(get_current_active_user), -): - """Retrieve a list of all organizations (version 2).""" - return organization.list_organizations_v2(state, regionId, current_user) + try: + # Convert each ORM instance to a Pydantic model + result = [GetVulnerabilityResponse.model_validate(v) for v in vulnerabilities] + except Exception as e: + raise HTTPException(status_code=500, detail=f"Serialization error: {str(e)}") + + return VulnerabilitySearchResponse(result=result, count=count) @api_router.post( - "/search/organizations", + "/vulnerabilities/export", dependencies=[Depends(get_current_active_user)], - tags=["Organizations"], + tags=["Vulnerabilities"], ) -async def search_organizations( - search_body: OrganizationSchema.OrganizationSearchBody, +async def get_export_vulnerabilities( + vulnerability_search: VulnerabilitySearch, current_user: User = Depends(get_current_active_user), ): - """Search for organizations in Elasticsearch.""" - return organization.search_organizations_task(search_body, current_user) - - -# ======================================== -# Search Endpoints -# ======================================== + """Export vulnerabilities.""" + return export_vulnerabilities(vulnerability_search, current_user) -@api_router.post( - "/search", +@api_router.get( + "/vulnerabilities/{vulnerability_id}", dependencies=[Depends(get_current_active_user)], - response_model=SearchResponse, - tags=["Search"], + response_model=GetVulnerabilityResponse, + tags=["Vulnerabilities"], ) -async def search( - search_body: DomainSearchBody, current_user: User = Depends(get_current_active_user) +async def call_get_vulnerability_by_id( + vulnerability_id, current_user: User = Depends(get_current_active_user) ): - """ElasticSearch get domains index.""" - try: - return await search_post(search_body, current_user) - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e) - ) + """Get vulnerability by id.""" + return get_vulnerability_by_id(vulnerability_id, current_user) -@api_router.post( - "/search/export", dependencies=[Depends(get_current_active_user)], tags=["Search"] +@api_router.put( + "/vulnerabilities/{vulnerability_id}", + dependencies=[Depends(get_current_active_user)], + response_model=VulnerabilitySchema, + tags=["Vulnerabilities"], ) -async def export_endpoint( - search_body: DomainSearchBody, current_user: User = Depends(get_current_active_user) +async def call_update_vulnerability( + vulnerability_id, + data: VulnerabilitySchema, + current_user: User = Depends(get_current_active_user), ): - try: - result = await search_export(search_body, current_user) - return result - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) + """ + Update vulnerability by id. + + Returns: + object: a single vulnerability object that has been modified. + """ + return update_vulnerability(vulnerability_id, data, current_user)