import base64 import io import json import logging import oci import os import requests import sys from fdk import response import osvc def handler(ctx, data: io.BytesIO=None): #logging.getLogger().debug("Thread Parse Function Called") try: # Patch operations have no response by default. Set a response here that will be overriden if errors occur. fn_response = {"message": "success"} fn_status_code = 200, fn_headers = { "Content-Type": "application/json" } app_id = ctx.AppID() #logging.getLogger().debug("App ID: {}".format(app_id)) # Params that will be assigned based on where this function is running username = None password = None # Get environment vars from F(n) hostname = os.getenv("b2cservice_hostname") # If the function is running in OCI, then retrieve secrets from vault if 'oci' in app_id: #logging.getLogger().debug("Using OCI config settings") username_ocid = os.getenv("b2cservice_username_ocid") password_ocid = os.getenv("b2cservice_password_ocid") # By default this will hit the auth service in the region the instance is running. signer = oci.auth.signers.get_resource_principals_signer() # Get instance principal context secret_client = oci.secrets.SecretsClient(config={}, signer=signer) # Get B2CService Credentials from Vault username = read_secret_value(secret_client, username_ocid) password = read_secret_value(secret_client, password_ocid) # else, if running locally, then use env params else: #logging.getLogger().debug("Using local config settings") username = os.getenv("b2cservice_username") password = os.getenv("b2cservice_password") if username == None: raise Exception("Could not get B2C Service username") if password == None: raise Exception("Could not get B2C Service password") osvc_client = osvc.OSvC(hostname, username, password) if osvc_client == None: raise Exception("Could not create an osvc_client object") # Parse Webook Body try: webhook_data_str = data.getvalue().decode('utf-8') logging.getLogger().debug(webhook_data_str) webhook_data = json.loads(webhook_data_str) except: raise Exception("Function did not receive a payload.") if 'body' in webhook_data: sms_body = webhook_data['body'] sms_from = webhook_data['from'] sms_to = webhook_data['to'] # Check to see if there is a contact with the "to" phone number contact_id = get_contact_by_mobile(osvc_client, sms_from) # If no contact was returned, then create one if contact_id == None: contact_id = create_contact_from_sms(osvc_client, sms_from) # Create an incident from the inbound thread new_incident = create_incident_from_sms(osvc_client, contact_id, webhook_data) else: raise Exception("Webhook payload does not include body data.") except (Exception, ValueError) as ex: exception_type, exception_object, exception_traceback = sys.exc_info() filename = exception_traceback.tb_frame.f_code.co_filename line_number = exception_traceback.tb_lineno logging.getLogger().error(json.dumps({ "file": filename, "line": line_number, "exception": str(ex) })) fn_response["message"] = str(ex) fn_status_code = 500 #logging.getLogger().debug("Thread Parse Function Ended") return response.Response(ctx, response_data=json.dumps(fn_response), headers=fn_headers, status_code=fn_status_code ) # Retrieve secret def read_secret_value(secret_client, secret_id): response = secret_client.get_secret_bundle(secret_id) base64_Secret_content = response.data.secret_bundle_content.content base64_secret_bytes = base64_Secret_content.encode('ascii') base64_message_bytes = base64.b64decode(base64_secret_bytes) secret_content = base64_message_bytes.decode('ascii') return secret_content def get_contact_by_mobile(osvc_client, number) : contact_results = osvc_client.get("/services/rest/connect/latest/contacts?q=phones.number = '{}'".format(number)) logging.getLogger().debug(json.dumps(contact_results, indent=3)) if 'items' in contact_results and len(contact_results['items']) > 0 and 'id' in contact_results['items'][0]: return contact_results['items'][0]['id'] return None def create_contact_from_sms(osvc_client, number): contact = { "name": { "first": "SMS", "last": "User" } } create_result = osvc_client.post("/services/rest/connect/latest/contacts", json.dumps(contact)) if 'id' in create_result and create_result['id'] > 0: contact_id = create_result['id'] new_phone = { "number": number, "phoneType": { "lookupName": "Mobile Phone" } } osvc_client.post("/services/rest/connect/latest/contacts/{}/phones".format(contact_id), json.dumps(new_phone)) return contact_id else: logging.getLogger().error("Could not get contact ID from create results") raise Exception(create_result) def create_incident_from_sms(osvc_client, contact_id, webhook_data): subject = webhook_data['body'] new_incident = { "subject": subject, "primaryContact": { "id": contact_id } } create_result = osvc_client.post("/services/rest/connect/latest/incidents", json.dumps(new_incident)) if 'id' in create_result and create_result['id'] > 0: incident_id = create_result['id'] # save the SMS body as a note to the incident for record keeping new_thread = { "text": json.dumps(webhook_data, indent=2), "entryType": { "lookupName": "Note" }, "contentType": { "lookupName": "text/plain" } } logging.getLogger().debug(json.dumps(new_thread, indent=3)) osvc_client.post("/services/rest/connect/latest/incidents/{}/threads".format(incident_id), json.dumps(new_thread)) return create_result else: logging.getLogger().error("Could not get incident ID from create results") raise Exception(create_result)