main
py 864 lines 30.6 KB
Raw
1 from fastapi import APIRouter
2 from fastapi import Depends
3 from fastapi import HTTPException
4 from fastapi import Query
5 from fastapi import Security
6 from loguru import logger
7 from sqlalchemy.exc import IntegrityError
8 from sqlalchemy.ext.asyncio import AsyncSession
9 from sqlalchemy.future import select
10 from starlette.status import HTTP_401_UNAUTHORIZED
11
12 from app.auth.utils import AuthHandler
13
14 # App specific imports
15 from app.customers.schema.customers import AgentModel
16 from app.customers.schema.customers import AgentsResponse
17 from app.customers.schema.customers import CustomerFullResponse
18 from app.customers.schema.customers import CustomerMetaRequestBody
19 from app.customers.schema.customers import CustomerMetaResponse
20 from app.customers.schema.customers import CustomerRequestBody
21 from app.customers.schema.customers import CustomerResponse
22 from app.customers.schema.customers import CustomersResponse
23 from app.customers.schema.customers import DeleteCustomerResponse
24 from app.db.db_session import get_db
25 from app.db.universal_models import Agents
26 from app.db.universal_models import Customers
27 from app.db.universal_models import CustomersMeta
28 from app.healthchecks.agents.schema.agents import AgentHealthCheckResponse
29 from app.healthchecks.agents.schema.agents import TimeCriteriaModel
30 from app.healthchecks.agents.services.agents import velociraptor_agents_healthcheck
31 from app.healthchecks.agents.services.agents import wazuh_agents_healthcheck
32 from app.middleware.license import is_feature_enabled
33
34 customers_router = APIRouter()
35
36
37 def verify_admin(user):
38 """
39 Verify if the user is an admin.
40
41 Args:
42 user: The user object to be verified.
43
44 Raises:
45 HTTPException: If the user is not an admin.
46
47 Returns:
48 None
49 """
50 if not user.is_admin:
51 raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
52
53
54 async def verify_unique_customer_code(
55 session: AsyncSession,
56 customer: CustomerRequestBody,
57 ):
58 """
59 Verifies if the given customer code is unique in the database.
60
61 Args:
62 session (AsyncSession): The database session.
63 customer (CustomerRequestBody): The customer data to be verified.
64
65 Raises:
66 HTTPException: If a customer with the same customer code already exists in the database.
67 """
68 stmt = select(Customers).filter(Customers.customer_code == customer.customer_code)
69 result = await session.execute(stmt)
70 existing_customer = result.scalars().first()
71 if existing_customer:
72 raise HTTPException(
73 status_code=400,
74 detail="Customer with this customer_code already exists",
75 )
76
77
78 # async def mssp_license_check(session: AsyncSession):
79 # """
80 # Check if the current number of provisioned customers is within the allowed range based on the MSSP license type.
81 # Customer 0 is free, 1-5 customers require an "MSSP 1-5" license, and 6-10 customers require an "MSSP 6-10" license.
82
83 # Args:
84 # session (AsyncSession): The database session.
85
86 # Raises:
87 # HTTPException: If the MSSP is not allowed to provision more customers.
88 # """
89 # # Select all customers to check the number of provisioned customers
90 # stmt = select(Customers)
91 # result = await session.execute(stmt)
92 # customers = result.scalars().all()
93 # provisioned_customers = len(customers)
94 # logger.info(f"Provisioned customers: {provisioned_customers}")
95
96 # if 1 <= provisioned_customers <= 5:
97 # # Check the license of the MSSP if the number of provisioned customers is between 1 and 5
98 # try:
99 # await is_feature_enabled(
100 # "MSSP 5",
101 # session,
102 # message="You have reached the maximum number of customers allowed for your license type. Please upgrade your license to provision more customers.",
103 # )
104 # except HTTPException as e:
105 # if e.status_code == 400:
106 # # If MSSP 1-5 license check fails, check for MSSP 6-10 or MSSP Unlimited license
107 # try:
108 # await is_feature_enabled(
109 # "MSSP 10",
110 # session,
111 # message="You have reached the maximum number of customers allowed for your license type. Please upgrade your license to provision more customers.",
112 # )
113 # except HTTPException as e2:
114 # if e2.status_code == 400:
115 # await is_feature_enabled(
116 # "MSSP Unlimited",
117 # session,
118 # message="You have reached the maximum number of customers allowed for your license type. Please upgrade your license to provision more customers.",
119 # )
120 # else:
121 # raise e2
122 # else:
123 # raise e
124 # elif 6 <= provisioned_customers <= 10:
125 # # Check the license of the MSSP if the number of provisioned customers is between 6 and 10
126 # await is_feature_enabled(
127 # "MSSP 10",
128 # session,
129 # message="You have reached the maximum number of customers allowed for your license type. Please upgrade your license to provision more customers.",
130 # )
131 # elif provisioned_customers > 10:
132 # await is_feature_enabled(
133 # "MSSP Unlimited",
134 # session,
135 # message="You have reached the maximum number of customers allowed for your license type. Please upgrade your license to provision more customers.",
136 # )
137
138
139 async def mssp_license_check(session: AsyncSession):
140 """
141 Check if the current number of provisioned customers is within the allowed range based on the MSSP license type.
142 - First customer is free (no license check)
143 - MSSP Unlimited: No limit
144 - MSSP 10: Up to 10 customers (0-9 current customers)
145 - MSSP 5: Up to 5 customers (0-4 current customers)
146 """
147 stmt = select(Customers)
148 result = await session.execute(stmt)
149 customers = result.scalars().all()
150 provisioned_customers = len(customers)
151 logger.info(f"Provisioned customers: {provisioned_customers}")
152
153 # Skip license check for first customer
154 if provisioned_customers == 0:
155 return
156
157 error_message = "You have reached the maximum number of customers allowed for your license type. Please upgrade your license to provision more customers."
158
159 # Try most permissive license first
160 try:
161 await is_feature_enabled("MSSP Unlimited", session, message=error_message)
162 return # License check passed
163 except HTTPException as e:
164 if e.status_code != 400:
165 raise e
166
167 # Check MSSP 10 license - adding new customer must not exceed 10
168 if provisioned_customers < 10:
169 try:
170 await is_feature_enabled("MSSP 10", session, message=error_message)
171 return # License check passed
172 except HTTPException as e:
173 if e.status_code != 400:
174 raise e
175
176 # Check MSSP 5 license - adding new customer must not exceed 5
177 if provisioned_customers < 5:
178 try:
179 await is_feature_enabled("MSSP 5", session, message=error_message)
180 return # License check passed
181 except HTTPException as e:
182 if e.status_code != 400:
183 raise e
184
185 raise HTTPException(status_code=400, detail=error_message)
186
187
188 @customers_router.post(
189 "",
190 response_model=CustomerResponse,
191 description="Create a new customer",
192 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
193 )
194 async def create_customer(
195 customer: CustomerRequestBody,
196 session: AsyncSession = Depends(get_db),
197 ) -> CustomerResponse:
198 """
199 Create a new customer.
200
201 Args:
202 customer (CustomerRequestBody): The customer data to be created.
203 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
204
205 Returns:
206 CustomerResponse: The response containing the created customer data.
207
208 Raises:
209 None
210 """
211 await mssp_license_check(session)
212 await verify_unique_customer_code(session, customer)
213 logger.info(f"Creating new customer: {customer}")
214 new_customer = Customers(**customer.model_dump())
215 session.add(new_customer)
216 await session.commit()
217 return CustomerResponse(
218 customer=customer,
219 success=True,
220 message="Customer created successfully",
221 )
222
223
224 @customers_router.get(
225 "",
226 response_model=CustomersResponse,
227 description="Get all customers",
228 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
229 )
230 async def get_customers(session: AsyncSession = Depends(get_db)) -> CustomersResponse:
231 """
232 Fetches all customers from the database.
233
234 Args:
235 session (AsyncSession): The async session object used to interact with the database.
236
237 Returns:
238 CustomersResponse: The response containing the list of customers fetched successfully.
239 """
240 logger.info("Fetching all customers")
241
242 # Asynchronous query to fetch all customers
243 result = await session.execute(select(Customers))
244 customers = result.scalars().all()
245
246 # Fetch all customer meta records to check provisioning status
247 meta_result = await session.execute(select(CustomersMeta))
248 customer_metas = meta_result.scalars().all()
249 provisioned_codes = {meta.customer_code for meta in customer_metas}
250
251 # Parse the customer ORM objects into schema objects and add is_provisioned field
252 customers_list = []
253 for customer in customers:
254 customer_data = CustomerRequestBody.from_orm(customer)
255 customer_data.is_provisioned = customer.customer_code in provisioned_codes
256 customers_list.append(customer_data)
257
258 return CustomersResponse(
259 customers=customers_list,
260 success=True,
261 message="Customers fetched successfully",
262 )
263
264
265 @customers_router.get(
266 "/{customer_code}",
267 response_model=CustomerResponse,
268 description="Get customer by customer_code",
269 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
270 )
271 async def get_customer(
272 customer_code: str,
273 session: AsyncSession = Depends(get_db),
274 ) -> CustomerResponse:
275 """
276 Get customer by customer_code.
277
278 Args:
279 customer_code (str): The code of the customer to retrieve.
280 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
281
282 Returns:
283 CustomerResponse: The response containing the customer data.
284
285 Raises:
286 HTTPException: If the customer with the specified code is not found.
287 """
288 logger.info(f"Fetching customer with customer_code: {customer_code}")
289
290 # Asynchronous query to fetch customer
291 result = await session.execute(
292 select(Customers).filter(Customers.customer_code == customer_code),
293 )
294 customer = result.scalars().first()
295
296 if not customer:
297 raise HTTPException(
298 status_code=404,
299 detail=f"Customer with customer_code {customer_code} not found",
300 )
301
302 # Convert ORM object to Pydantic model
303 customer_data = CustomerRequestBody.from_orm(customer)
304 return CustomerResponse(
305 customer=customer_data,
306 success=True,
307 message="Customer fetched successfully",
308 )
309
310
311 @customers_router.put(
312 "/{customer_code}",
313 response_model=CustomerResponse,
314 description="Update customer by customer_code",
315 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
316 )
317 async def update_customer(
318 customer_code: str,
319 customer: CustomerRequestBody,
320 session: AsyncSession = Depends(get_db),
321 ) -> CustomerResponse:
322 """
323 Update a customer with the given customer_code.
324
325 Args:
326 customer_code (str): The code of the customer to be updated.
327 customer (CustomerRequestBody): The updated customer data.
328 session (AsyncSession, optional): The asynchronous database session. Defaults to Depends(get_db).
329
330 Returns:
331 CustomerResponse: The response containing the updated customer data.
332
333 Raises:
334 HTTPException: If the customer with the given customer_code is not found.
335 """
336 logger.info(f"Updating customer with customer_code: {customer_code}")
337
338 # Asynchronous query to find the existing customer
339 result = await session.execute(
340 select(Customers).filter(Customers.customer_code == customer_code),
341 )
342 existing_customer = result.scalars().first()
343
344 if not existing_customer:
345 raise HTTPException(
346 status_code=404,
347 detail=f"Customer with customer_code {customer_code} not found",
348 )
349
350 # Update model instance with input data
351 for key, value in customer.model_dump(exclude={"is_provisioned"}).items():
352 setattr(existing_customer, key, value)
353
354 await session.commit() # Commit changes asynchronously
355
356 return CustomerResponse(
357 customer=customer, # CustomerRequestBody is already a Pydantic model
358 success=True,
359 message="Customer updated successfully",
360 )
361
362
363 # ! TODO - Add a check to ensure that the customer_code is not being used by any agents
364 @customers_router.delete(
365 "/{customer_code}",
366 response_model=DeleteCustomerResponse,
367 description="Delete customer by customer_code",
368 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
369 )
370 async def delete_customer(
371 customer_code: str,
372 session: AsyncSession = Depends(get_db),
373 ) -> DeleteCustomerResponse:
374 """
375 Delete a customer by customer_code.
376
377 Args:
378 customer_code (str): The code of the customer to be deleted.
379 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
380
381 Returns:
382 DeleteCustomerResponse: The response containing the deletion status.
383
384 Raises:
385 HTTPException: If the customer with the given customer_code is not found or has associated users.
386 """
387 logger.info(f"Deleting customer with customer_code: {customer_code}")
388
389 result = await session.execute(
390 select(Customers).filter(Customers.customer_code == customer_code),
391 )
392 existing_customer = result.scalars().first()
393
394 if not existing_customer:
395 raise HTTPException(
396 status_code=404,
397 detail=f"Customer with customer_code {customer_code} not found",
398 )
399
400 try:
401 # Delete the customer
402 await session.delete(existing_customer)
403 await session.flush() # Flush to trigger any constraint violations
404 await session.commit() # Commit the transaction
405 await session.close() # Close the session
406
407 logger.info(f"Successfully deleted customer with customer_code: {customer_code}")
408 return DeleteCustomerResponse(
409 success=True,
410 message=f"Customer '{customer_code}' deleted successfully",
411 )
412
413 except IntegrityError as e:
414 await session.rollback()
415 error_message = str(e.orig)
416
417 # Check if it's the user_customer_access foreign key constraint
418 if "user_customer_access" in error_message and "FOREIGN KEY" in error_message:
419 logger.error(f"Cannot delete customer {customer_code}: users are still assigned to this customer")
420 raise HTTPException(
421 status_code=400,
422 detail=f"Cannot delete customer '{customer_code}'. There are still users assigned to this customer. Please remove all user assignments before deleting the customer.",
423 )
424
425 # For other integrity errors
426 logger.error(f"Integrity error deleting customer {customer_code}: {error_message}")
427 raise HTTPException(
428 status_code=400,
429 detail="Cannot delete customer due to existing dependencies. Please ensure all related data is removed first.",
430 )
431
432 except Exception as e:
433 await session.rollback()
434 logger.error(f"Error deleting customer {customer_code}: {str(e)}")
435 raise HTTPException(
436 status_code=500,
437 detail=f"An error occurred while deleting the customer: {str(e)}",
438 )
439
440
441 @customers_router.post(
442 "/{customer_code}/meta",
443 response_model=CustomerMetaResponse,
444 description="Add new customer meta",
445 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
446 deprecated=True,
447 )
448 async def add_customer_meta(
449 customer_code: str,
450 customer_meta: CustomerMetaRequestBody,
451 session: AsyncSession = Depends(get_db),
452 ) -> CustomerMetaResponse:
453 """
454 Add new customer meta.
455
456 Args:
457 customer_code (str): The code of the customer.
458 customer_meta (CustomerMetaRequestBody): The meta information of the customer.
459 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
460
461 Returns:
462 CustomerMetaResponse: The response containing the added customer meta.
463
464 Raises:
465 HTTPException: If the customer with the given customer_code is not found.
466 """
467 logger.info(f"Adding new customer meta: {customer_meta}")
468
469 result = await session.execute(
470 select(Customers).filter(Customers.customer_code == customer_code),
471 )
472 existing_customer = result.scalars().first()
473
474 if not existing_customer:
475 raise HTTPException(
476 status_code=404,
477 detail=f"Customer with customer_code {customer_code} not found",
478 )
479
480 logger.info(f"Got existing customer: {existing_customer}")
481 new_customer_meta = CustomersMeta(**customer_meta.model_dump())
482 new_customer_meta.customer_code = existing_customer.customer_code
483 new_customer_meta.customer_name = existing_customer.customer_name
484
485 session.add(new_customer_meta)
486 await session.commit() # Use await to perform the commit operation asynchronously
487
488 return CustomerMetaResponse(
489 customer_meta=customer_meta,
490 success=True,
491 message="Customer meta added successfully",
492 )
493
494
495 @customers_router.get(
496 "/{customer_code}/meta",
497 response_model=CustomerMetaResponse,
498 description="Get customer meta by customer_code",
499 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
500 deprecated=True,
501 )
502 async def get_customer_meta(
503 customer_code: str,
504 session: AsyncSession = Depends(get_db),
505 ) -> CustomerMetaResponse:
506 """
507 Retrieve customer meta data by customer_code.
508
509 Args:
510 customer_code (str): The customer code.
511 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
512
513 Returns:
514 CustomerMetaResponse: The response containing the customer meta data.
515
516 Raises:
517 HTTPException: If the customer meta data is not found.
518 """
519 logger.info(f"Fetching customer meta with customer_code: {customer_code}")
520
521 result = await session.execute(
522 select(CustomersMeta).filter(CustomersMeta.customer_code == customer_code),
523 )
524 customer_meta = result.scalars().first()
525
526 if not customer_meta:
527 raise HTTPException(
528 status_code=404,
529 detail=f"Customer meta with customer_code {customer_code} not found",
530 )
531
532 # Assuming CustomerMetaRequestBody can be created from the ORM model directly
533 customer_meta_data = CustomerMetaRequestBody.from_orm(customer_meta)
534 return CustomerMetaResponse(
535 customer_meta=customer_meta_data,
536 success=True,
537 message="Customer meta fetched successfully",
538 )
539
540
541 @customers_router.put(
542 "/{customer_code}/meta",
543 response_model=CustomerMetaResponse,
544 description="Update customer meta by customer_code",
545 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
546 deprecated=True,
547 )
548 async def update_customer_meta(
549 customer_code: str,
550 customer_meta: CustomerMetaRequestBody,
551 session: AsyncSession = Depends(get_db),
552 ) -> CustomerMetaResponse:
553 """
554 Update customer meta by customer_code.
555
556 Args:
557 customer_code (str): The customer code.
558 customer_meta (CustomerMetaRequestBody): The updated customer meta.
559 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
560
561 Returns:
562 CustomerMetaResponse: The updated customer meta response.
563
564 Raises:
565 HTTPException: If the customer meta with the given customer_code is not found.
566 """
567 logger.info(f"Updating customer meta with customer_code: {customer_code}")
568
569 result = await session.execute(
570 select(CustomersMeta).filter(CustomersMeta.customer_code == customer_code),
571 )
572 existing_customer_meta = result.scalars().first()
573
574 if not existing_customer_meta:
575 raise HTTPException(
576 status_code=404,
577 detail=f"Customer meta with customer_code {customer_code} not found",
578 )
579
580 # Update the existing record with new values
581 for key, value in customer_meta.model_dump(exclude_unset=True).items():
582 setattr(existing_customer_meta, key, value)
583
584 await session.commit() # Commit the changes to the database asynchronously
585
586 # Return the updated customer_meta
587 return CustomerMetaResponse(
588 customer_meta=customer_meta,
589 success=True,
590 message="Customer meta updated successfully",
591 )
592
593
594 @customers_router.delete(
595 "/{customer_code}/meta",
596 response_model=CustomerMetaResponse,
597 description="Delete customer meta by customer_code",
598 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
599 deprecated=False,
600 )
601 async def delete_customer_meta(
602 customer_code: str,
603 session: AsyncSession = Depends(get_db),
604 ) -> CustomerMetaResponse:
605 """
606 Delete customer meta by customer_code.
607
608 Args:
609 customer_code (str): The code of the customer meta to be deleted.
610 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
611
612 Returns:
613 CustomerMetaResponse: The response containing the deleted customer meta data.
614
615 Raises:
616 HTTPException: If the customer meta with the given customer_code is not found.
617 """
618 logger.info(f"Deleting customer meta with customer_code: {customer_code}")
619
620 result = await session.execute(
621 select(CustomersMeta).filter(CustomersMeta.customer_code == customer_code),
622 )
623 existing_customer_meta = result.scalars().first()
624
625 if not existing_customer_meta:
626 raise HTTPException(
627 status_code=404,
628 detail=f"Customer meta with customer_code {customer_code} not found",
629 )
630
631 # Store customer meta data for response before deleting
632 customer_meta_data = CustomerMetaRequestBody.from_orm(existing_customer_meta)
633
634 await session.delete(existing_customer_meta)
635 await session.flush() # Optional: Flush the changes to the database
636 await session.commit() # Ensure to await commit
637 # Close the session
638 await session.close()
639
640 return CustomerMetaResponse(
641 customer_meta=customer_meta_data,
642 success=True,
643 message="Customer meta deleted successfully",
644 )
645
646
647 @customers_router.get(
648 "/{customer_code}/full",
649 response_model=CustomerFullResponse,
650 description="Get customer and customer meta by customer_code",
651 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
652 )
653 async def get_customer_full(
654 customer_code: str,
655 session: AsyncSession = Depends(get_db),
656 ) -> CustomerFullResponse:
657 """
658 Retrieve the customer and customer meta information based on the customer code.
659
660 Args:
661 customer_code (str): The code of the customer to retrieve.
662 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
663
664 Returns:
665 CustomerFullResponse: The response containing the customer and customer meta information.
666
667 Raises:
668 HTTPException: If the customer with the specified code is not found.
669
670 """
671 logger.info(
672 f"Fetching customer and customer meta with customer_code: {customer_code}",
673 )
674
675 customer_result = await session.execute(
676 select(Customers).filter(Customers.customer_code == customer_code),
677 )
678 customer = customer_result.scalars().first()
679 if not customer:
680 raise HTTPException(
681 status_code=404,
682 detail=f"Customer with customer_code {customer_code} not found",
683 )
684
685 customer_meta_result = await session.execute(
686 select(CustomersMeta).filter(CustomersMeta.customer_code == customer_code),
687 )
688 customer_meta = customer_meta_result.scalars().first()
689 if not customer_meta:
690 return CustomerFullResponse(
691 customer=CustomerRequestBody.from_orm(customer),
692 success=True,
693 message="Customer fetched successfully but customer meta not found",
694 )
695
696 return CustomerFullResponse(
697 customer=CustomerRequestBody.from_orm(customer),
698 customer_meta=CustomerMetaRequestBody.from_orm(customer_meta),
699 success=True,
700 message="Customer and customer meta fetched successfully",
701 )
702
703
704 @customers_router.get(
705 "/{customer_code}/agents",
706 response_model=AgentsResponse,
707 description="Get agents for the given customer_code",
708 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
709 )
710 async def get_agents(
711 customer_code: str,
712 session: AsyncSession = Depends(get_db),
713 ) -> AgentsResponse:
714 """
715 Fetches agents for the given customer_code.
716
717 Args:
718 customer_code (str): The code of the customer.
719 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
720
721 Returns:
722 AgentsResponse: The response containing the fetched agents.
723 """
724 logger.info(f"Fetching agents for customer_code: {customer_code}")
725
726 # Check if the customer exists
727 customer_result = await session.execute(
728 select(Customers).filter(Customers.customer_code == customer_code),
729 )
730 customer = customer_result.scalars().first()
731 if not customer:
732 raise HTTPException(
733 status_code=404,
734 detail=f"Customer with customer_code {customer_code} not found",
735 )
736
737 # Asynchronously fetch all agents for the customer
738 agents_result = await session.execute(
739 select(Agents).filter(Agents.customer_code == customer_code),
740 )
741 agents = agents_result.scalars().all()
742
743 # Convert ORM objects to Pydantic models
744 agents_list = [AgentModel.from_orm(agent) for agent in agents]
745 return AgentsResponse(
746 agents=agents_list,
747 success=True,
748 message="Agents fetched successfully",
749 )
750
751
752 @customers_router.get(
753 "/{customer_code}/agents/healthcheck/wazuh",
754 response_model=AgentHealthCheckResponse,
755 description="Get agents healthcheck for the given customer_code",
756 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
757 )
758 async def get_wazuh_agents_healthcheck(
759 customer_code: str,
760 session: AsyncSession = Depends(get_db),
761 minutes: int = Query(
762 60,
763 description="Number of minutes within which the agent should have been last seen to be considered healthy.",
764 ),
765 hours: int = Query(
766 0,
767 description="Number of hours within which the agent should have been last seen to be considered healthy.",
768 ),
769 days: int = Query(
770 0,
771 description="Number of days within which the agent should have been last seen to be considered healthy.",
772 ),
773 ) -> AgentHealthCheckResponse:
774 """
775 Get agents healthcheck for the given customer_code.
776
777 Args:
778 customer_code (str): The code of the customer.
779
780 Returns:
781 AgentHealthCheckResponse: The response containing the healthcheck information for the agents.
782 """
783 logger.info(f"Fetching agents for customer_code: {customer_code}")
784
785 # Asynchronously fetch customer and agents
786 customer_result = await session.execute(
787 select(Customers).filter(Customers.customer_code == customer_code),
788 )
789 customer = customer_result.scalars().first()
790 if not customer:
791 raise HTTPException(
792 status_code=404,
793 detail=f"Customer with customer_code {customer_code} not found",
794 )
795
796 agents_result = await session.execute(
797 select(Agents).filter(Agents.customer_code == customer_code),
798 )
799 agents = agents_result.scalars().all()
800
801 # Convert ORM objects to Pydantic models
802
803 # Explode the agents list into a list of Agent objects
804 agents = [AgentModel.parse_obj(agent.__dict__) for agent in agents]
805 time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
806 return await wazuh_agents_healthcheck(agents, time_criteria)
807
808
809 @customers_router.get(
810 "/{customer_code}/agents/healthcheck/velociraptor",
811 response_model=AgentHealthCheckResponse,
812 description="Get agents healthcheck for the given customer_code",
813 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
814 )
815 async def get_velociraptor_agents_healthcheck(
816 customer_code: str,
817 session: AsyncSession = Depends(get_db),
818 minutes: int = Query(
819 60,
820 description="Number of minutes within which the agent should have been last seen to be considered healthy.",
821 ),
822 hours: int = Query(
823 0,
824 description="Number of hours within which the agent should have been last seen to be considered healthy.",
825 ),
826 days: int = Query(
827 0,
828 description="Number of days within which the agent should have been last seen to be considered healthy.",
829 ),
830 ) -> AgentHealthCheckResponse:
831 """
832 Fetches the healthcheck of agents for the given customer_code.
833
834 Args:
835 customer_code (str): The code of the customer.
836 session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
837 minutes (int, optional): Number of minutes within which the agent should have been last seen to be considered healthy. Defaults to 60.
838 hours (int, optional): Number of hours within which the agent should have been last seen to be considered healthy. Defaults to 0.
839 days (int, optional): Number of days within which the agent should have been last seen to be considered healthy. Defaults to 0.
840
841 Returns:
842 AgentHealthCheckResponse: The response containing the healthcheck of agents.
843 """
844 logger.info(f"Fetching agents for customer_code: {customer_code}")
845
846 # Asynchronously fetch customer
847 customer_result = await session.execute(
848 select(Customers).filter(Customers.customer_code == customer_code),
849 )
850 customer = customer_result.scalars().first()
851 if not customer:
852 raise HTTPException(
853 status_code=404,
854 detail=f"Customer with customer_code {customer_code} not found",
855 )
856
857 # Asynchronously fetch all agents for the customer
858 agents_result = await session.execute(
859 select(Agents).filter(Agents.customer_code == customer_code),
860 )
861 agents = agents_result.scalars().all()
862 agents = [AgentModel.parse_obj(agent.__dict__) for agent in agents]
863 time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
864 return await velociraptor_agents_healthcheck(agents, time_criteria)