@cryptotaxi247 / CoPilot / commits / 4285be2b

Office365 customer code (#433)

* feat: add placeholder for customer code mapping logic in alert creation * feat: implement customer code retrieval by integration auth key

taylor_socfortress committed Apr 2, 2025 at 11:21 UTC 4285be2ba8e091a214d982141e3f2b6e31ed16c6
3 files changed +141 -2
backend/app/incidents/services/incident_alert.py
+62 -2
@@ -44,6 +44,7 @@ from app.integrations.alert_creation_settings.models.alert_creation_settings imp
44 AlertCreationSettings,
45 )
46 from app.integrations.alert_escalation.schema.escalate_alert import CustomerCodeKeys
47 +from app.integrations.routes import get_customer_by_auth_key
48
49
50 async def fetch_settings(field: str, value: str, session: AsyncSession):
@@ -203,7 +204,45 @@ async def construct_soc_alert_url(root_url: str, soc_alert_id: int) -> str:
204 return f"{root_url}{url_path}"
205
206
206 -async def get_customer_code(alert_details: dict):
207 +# async def get_customer_code(alert_details: dict):
208 +# logger.info(f"Fetching customer code for alert {alert_details}")
209 +
210 +# # Iterate over the possible keys and return the value if the key is present
211 +# for key in CustomerCodeKeys:
212 +# logger.info(f"Checking for key {key.value}")
213 +# if key.value in alert_details:
214 +# value = alert_details[key.value]
215 +# if key == CustomerCodeKeys.CLUSTER_NODE:
216 +# processed_value = CustomerCodeKeys.get_processed_value(key, value)
217 +# logger.info(f"Processed value for {key.value} is {processed_value}")
218 +# return processed_value
219 +# return value
220 +
221 +# # If none of the keys are present, raise an exception
222 +# logger.info(f"Failed to fetch customer code. Valid customer code field names are {', '.join([key.value for key in CustomerCodeKeys])}")
223 +# raise HTTPException(
224 +# status_code=400,
225 +# detail=f"Failed to fetch customer code. Valid customer code field names are {', '.join([key.value for key in CustomerCodeKeys])}",
226 +# )
227 +
228 +
229 +async def get_customer_code(alert_details: dict, session: AsyncSession = None):
230 + """
231 + Fetch the customer code from alert details.
232 +
233 + For Office365 organization IDs, uses the integration auth key lookup service.
234 + For other customer code types, uses the standard lookup process.
235 +
236 + Args:
237 + alert_details (dict): The alert details dictionary containing potential customer code fields
238 + session (AsyncSession, optional): Database session for integration lookups
239 +
240 + Returns:
241 + str: The customer code
242 +
243 + Raises:
244 + HTTPException: If no valid customer code can be found
245 + """
246 logger.info(f"Fetching customer code for alert {alert_details}")
247
248 # Iterate over the possible keys and return the value if the key is present
@@ -211,10 +250,31 @@ async def get_customer_code(alert_details: dict):
250 logger.info(f"Checking for key {key.value}")
251 if key.value in alert_details:
252 value = alert_details[key.value]
253 +
254 + # Handle Office365 OrganizationId specially - lookup from integrations
255 + if key == CustomerCodeKeys.DATA_OFFICE365_ORGANIZATION_ID and session:
256 + try:
257 + logger.info(f"Looking up customer by Office365 organization ID: {value}")
258 + customer_response = await get_customer_by_auth_key(
259 + integration_name="Office365",
260 + auth_key_name="TENANT_ID",
261 + auth_key_value=value,
262 + session=session,
263 + )
264 + logger.info(f"Found customer {customer_response.customer_code} for Office365 organization ID {value}")
265 + return customer_response.customer_code
266 + except HTTPException as e:
267 + logger.warning(f"Failed to get customer code from Office365 organization ID: {str(e)}")
268 + # Continue checking other keys if this lookup fails
269 + continue
270 +
271 + # Handle cluster node special processing
272 if key == CustomerCodeKeys.CLUSTER_NODE:
273 processed_value = CustomerCodeKeys.get_processed_value(key, value)
274 logger.info(f"Processed value for {key.value} is {processed_value}")
275 return processed_value
276 +
277 + # For other keys, return the value directly
278 return value
279
280 # If none of the keys are present, raise an exception
@@ -851,7 +911,7 @@ async def create_alert(
911 logger.info(f"Creating alert {alert.alert_id} in CoPilot")
912 alert_details = await get_single_alert_details(alert_details=alert)
913 await validate_syslog_type_source(alert_details.syslog_type, session)
854 - customer_code = await get_customer_code(dict(alert_details._source))
914 + customer_code = await get_customer_code(dict(alert_details._source), session=session)
915 logger.info(f"Customer code: {customer_code}")
916 customer_alert_creation_settings = await is_customer_code_valid(customer_code=customer_code, session=session)
917 logger.info(f"Customer creation settings: {customer_alert_creation_settings}")
backend/app/integrations/routes.py
+66
@@ -38,6 +38,7 @@ from app.integrations.schema import AuthKey
38 from app.integrations.schema import AvailableIntegrationsResponse
39 from app.integrations.schema import CreateIntegrationAuthKeys
40 from app.integrations.schema import CreateIntegrationService
41 +from app.integrations.schema import CustomerByAuthKeyResponse
42 from app.integrations.schema import CustomerIntegrationCreate
43 from app.integrations.schema import CustomerIntegrationCreateResponse
44 from app.integrations.schema import CustomerIntegrationDeleteResponse
@@ -1001,6 +1002,71 @@ async def delete_integration(
1002 return generate_decommission_response(customer_code, integration_name)
1003
1004
1005 +@integration_settings_router.get(
1006 + "/integration_customer/{integration_name}/{auth_key_name}/{auth_key_value}",
1007 + description="Get customer code by integration details and auth key value",
1008 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
1009 +)
1010 +async def get_customer_by_auth_key(
1011 + integration_name: str,
1012 + auth_key_name: str,
1013 + auth_key_value: str,
1014 + session: AsyncSession = Depends(get_db),
1015 +) -> CustomerByAuthKeyResponse:
1016 + """
1017 + Retrieve a customer code based on integration name, auth key name, and auth key value.
1018 +
1019 + This is useful for identifying which customer an integration belongs to when you only
1020 + have specific integration details, like a tenant ID.
1021 +
1022 + Args:
1023 + integration_name: The name of the integration (e.g., "Office365")
1024 + auth_key_name: The name of the auth key (e.g., "TENANT_ID")
1025 + auth_key_value: The value of the auth key to look up
1026 +
1027 + Returns:
1028 + Customer code and name associated with the provided integration details
1029 + """
1030 + try:
1031 + # Build query to find customer by auth key value
1032 + query = (
1033 + select(CustomerIntegrations.customer_code, CustomerIntegrations.customer_name)
1034 + .join(IntegrationSubscription, CustomerIntegrations.id == IntegrationSubscription.customer_id)
1035 + .join(IntegrationService, IntegrationSubscription.integration_service_id == IntegrationService.id)
1036 + .join(IntegrationAuthKeys, IntegrationSubscription.id == IntegrationAuthKeys.subscription_id)
1037 + .where(
1038 + IntegrationService.service_name == integration_name,
1039 + IntegrationAuthKeys.auth_key_name == auth_key_name,
1040 + IntegrationAuthKeys.auth_value == auth_key_value,
1041 + )
1042 + )
1043 +
1044 + # Execute query
1045 + result = await session.execute(query)
1046 + customer_info = result.first()
1047 +
1048 + if customer_info is None:
1049 + raise HTTPException(
1050 + status_code=404,
1051 + detail=f"No customer found with {integration_name} integration having {auth_key_name}={auth_key_value}",
1052 + )
1053 +
1054 + customer_code, customer_name = customer_info
1055 +
1056 + return CustomerByAuthKeyResponse(
1057 + customer_code=customer_code,
1058 + customer_name=customer_name,
1059 + integration_name=integration_name,
1060 + auth_key_name=auth_key_name,
1061 + )
1062 +
1063 + except HTTPException:
1064 + raise
1065 + except Exception as e:
1066 + logger.error(f"Error looking up customer by integration auth key: {str(e)}")
1067 + raise HTTPException(status_code=500, detail=f"Failed to look up customer: {str(e)}")
1068 +
1069 +
1070 # @integration_settings_router.delete(
1071 # "/delete_integration_meta",
1072 # response_model=CustomerIntegrationsMetaResponse,
backend/app/integrations/schema.py
+13
@@ -238,3 +238,16 @@ class CustomerIntegrationsMetaResponse(BaseModel):
238 None,
239 description="The customer integrations metadata.",
240 )
241 +
242 +
243 +class CustomerByAuthKeyResponse(BaseModel):
244 + """
245 + Response model for the get_customer_by_auth_key endpoint.
246 + """
247 +
248 + customer_code: str
249 + customer_name: str
250 + integration_name: str
251 + auth_key_name: str
252 + success: bool = True
253 + message: str = "Customer found successfully."