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."